mirror of
https://github.com/google/nomulus.git
synced 2025-06-28 23:33:36 +02:00
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:
parent
a41677aea1
commit
5012893c1d
2396 changed files with 0 additions and 0 deletions
49
java/google/registry/flows/BUILD
Normal file
49
java/google/registry/flows/BUILD
Normal file
|
@ -0,0 +1,49 @@
|
|||
package(
|
||||
default_visibility = ["//java/com/google/domain/registry:registry_project"],
|
||||
)
|
||||
|
||||
|
||||
filegroup(
|
||||
name = "flows_files",
|
||||
srcs = glob([
|
||||
"*.java",
|
||||
"**/*.java",
|
||||
]),
|
||||
)
|
||||
|
||||
java_library(
|
||||
name = "flows",
|
||||
srcs = glob([
|
||||
"*.java",
|
||||
"**/*.java",
|
||||
]),
|
||||
deps = [
|
||||
"//java/com/google/common/annotations",
|
||||
"//java/com/google/common/base",
|
||||
"//java/com/google/common/collect",
|
||||
"//java/com/google/common/io",
|
||||
"//java/com/google/common/net",
|
||||
"//java/com/google/domain/registry/config",
|
||||
"//java/com/google/domain/registry/dns",
|
||||
"//java/com/google/domain/registry/mapreduce",
|
||||
"//java/com/google/domain/registry/mapreduce/inputs",
|
||||
"//java/com/google/domain/registry/model",
|
||||
"//java/com/google/domain/registry/monitoring/whitebox",
|
||||
"//java/com/google/domain/registry/request",
|
||||
"//java/com/google/domain/registry/security:servlets",
|
||||
"//java/com/google/domain/registry/tldconfig/idn",
|
||||
"//java/com/google/domain/registry/tmch",
|
||||
"//java/com/google/domain/registry/util",
|
||||
"//java/com/google/domain/registry/xml",
|
||||
"//third_party/java/appengine:appengine-api",
|
||||
"//third_party/java/appengine_mapreduce2:appengine_mapreduce",
|
||||
"//third_party/java/dagger",
|
||||
"//third_party/java/joda_money",
|
||||
"//third_party/java/joda_time",
|
||||
"//third_party/java/jsr305_annotations",
|
||||
"//third_party/java/jsr330_inject",
|
||||
"//third_party/java/objectify:objectify-v4_1",
|
||||
"//third_party/java/servlet/servlet_api",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
119
java/google/registry/flows/EppConsoleServlet.java
Normal file
119
java/google/registry/flows/EppConsoleServlet.java
Normal file
|
@ -0,0 +1,119 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.appengine.api.users.UserServiceFactory.getUserService;
|
||||
import static com.google.common.base.Strings.nullToEmpty;
|
||||
import static com.google.domain.registry.flows.EppServletUtils.handleEppCommandAndWriteResponse;
|
||||
import static java.lang.System.identityHashCode;
|
||||
|
||||
import com.google.appengine.api.users.User;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.domain.registry.flows.EppException.AuthenticationErrorException;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.model.registrar.RegistrarContact;
|
||||
import com.google.domain.registry.security.XsrfProtectedServlet;
|
||||
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/** The {@link EppConsoleServlet} runs EPP from the console. It requires GAE user authentication. */
|
||||
public class EppConsoleServlet extends XsrfProtectedServlet {
|
||||
|
||||
/**
|
||||
* Credentials provided by the GAE User service.
|
||||
*
|
||||
* @see com.google.appengine.api.users.UserService
|
||||
*/
|
||||
public static final class GaeUserCredentials implements TransportCredentials {
|
||||
|
||||
/** User is not logged in as a GAE user. */
|
||||
public static class UserNotLoggedInException extends AuthenticationErrorException {
|
||||
public UserNotLoggedInException() {
|
||||
super("User is not logged in");
|
||||
}
|
||||
}
|
||||
|
||||
/** GAE user id is not allowed to login as requested registrar. */
|
||||
public static class BadGaeUserIdException extends AuthenticationErrorException {
|
||||
public BadGaeUserIdException(User user) {
|
||||
super(
|
||||
"User id is not allowed to login as requested registrar: "
|
||||
+ (nullToEmpty(user.getEmail())));
|
||||
}
|
||||
}
|
||||
|
||||
final User gaeUser;
|
||||
|
||||
@VisibleForTesting
|
||||
public GaeUserCredentials(@Nullable User gaeUser) {
|
||||
this.gaeUser = gaeUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performsLoginCheck() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Registrar r) throws AuthenticationErrorException {
|
||||
if (gaeUser == null) {
|
||||
throw new UserNotLoggedInException();
|
||||
}
|
||||
// Allow admins to act as any registrar.
|
||||
if (getUserService().isUserAdmin()) {
|
||||
return;
|
||||
}
|
||||
// Check Registrar's contacts to see if any are associated with this gaeUserId.
|
||||
final String gaeUserId = gaeUser.getUserId();
|
||||
for (RegistrarContact rc : r.getContacts()) {
|
||||
if (gaeUserId.equals(rc.getGaeUserId())) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new BadGaeUserIdException(gaeUser);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("GaeUserCredentials@%s{gaeUser: %s}", identityHashCode(this), gaeUser);
|
||||
}
|
||||
}
|
||||
|
||||
/** Used by related UI servlets to generate matching XSRF tokens. */
|
||||
public static final String XSRF_SCOPE = "console";
|
||||
|
||||
/** How long generated XSRF tokens for this scope remain valid. */
|
||||
public static final Duration XSRF_LIFETIME = Duration.standardDays(1);
|
||||
|
||||
public EppConsoleServlet() {
|
||||
super(XSRF_SCOPE, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
|
||||
handleEppCommandAndWriteResponse(
|
||||
ByteStreams.toByteArray(req.getInputStream()),
|
||||
rsp,
|
||||
new HttpSessionMetadata(
|
||||
new GaeUserCredentials(getUserService().getCurrentUser()), req.getSession(true)));
|
||||
}
|
||||
}
|
114
java/google/registry/flows/EppController.java
Normal file
114
java/google/registry/flows/EppController.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.EppXmlTransformer.marshalWithLenientRetry;
|
||||
import static com.google.domain.registry.flows.EppXmlTransformer.unmarshal;
|
||||
import static com.google.domain.registry.flows.FlowRegistry.getFlowClass;
|
||||
|
||||
import com.google.apphosting.api.ApiProxy;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.flows.FlowRunner.CommitMode;
|
||||
import com.google.domain.registry.flows.FlowRunner.UserPrivileges;
|
||||
import com.google.domain.registry.model.eppcommon.Trid;
|
||||
import com.google.domain.registry.model.eppinput.EppInput;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Response;
|
||||
import com.google.domain.registry.model.eppoutput.Result;
|
||||
import com.google.domain.registry.model.eppoutput.Result.Code;
|
||||
import com.google.domain.registry.monitoring.whitebox.EppMetrics;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
import com.google.domain.registry.util.SystemClock;
|
||||
|
||||
/**
|
||||
* The EppController class, which implements the state machine for the EPP command/response
|
||||
* protocol.
|
||||
*
|
||||
* @see "http://tools.ietf.org/html/rfc5730"
|
||||
*/
|
||||
public final class EppController {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
private static final Clock clock = new SystemClock();
|
||||
|
||||
/**
|
||||
* Read an EPP envelope from the client, find the matching flow, execute it, and return
|
||||
* the response marshalled to a byte array.
|
||||
*/
|
||||
public static byte[] handleEppCommand(byte[] inputXmlBytes, SessionMetadata sessionMetadata) {
|
||||
Trid trid = null;
|
||||
EppMetrics metrics = new EppMetrics();
|
||||
metrics.setRequestId(
|
||||
ApiProxy.getCurrentEnvironment().getAttributes().get(
|
||||
"com.google.appengine.runtime.request_log_id").toString());
|
||||
try {
|
||||
EppInput eppInput = unmarshal(inputXmlBytes);
|
||||
trid = Trid.create(eppInput.getCommandWrapper().getClTrid());
|
||||
ImmutableList<String> targetIds = eppInput.getTargetIds();
|
||||
metrics.setCommandName(eppInput.getCommandName());
|
||||
metrics.setClientId(sessionMetadata.getClientId());
|
||||
metrics.setPrivilegeLevel(
|
||||
sessionMetadata.isSuperuser()
|
||||
? UserPrivileges.SUPERUSER.toString()
|
||||
: UserPrivileges.NORMAL.toString());
|
||||
if (!targetIds.isEmpty()) {
|
||||
metrics.setEppTarget(Joiner.on(",").join(targetIds));
|
||||
}
|
||||
|
||||
FlowRunner flowRunner = new FlowRunner(
|
||||
getFlowClass(eppInput),
|
||||
eppInput,
|
||||
trid,
|
||||
sessionMetadata,
|
||||
inputXmlBytes,
|
||||
metrics);
|
||||
EppOutput eppOutput = flowRunner.run(
|
||||
sessionMetadata.isDryRun() ? CommitMode.DRY_RUN : CommitMode.LIVE,
|
||||
sessionMetadata.isSuperuser() ? UserPrivileges.SUPERUSER : UserPrivileges.NORMAL);
|
||||
if (eppOutput.isResponse()) {
|
||||
metrics.setEppStatus(eppOutput.getResponse().getResult().getCode());
|
||||
}
|
||||
return marshalWithLenientRetry(eppOutput);
|
||||
} catch (EppException e) {
|
||||
// The command failed. Send the client an error message.
|
||||
metrics.setEppStatus(e.getResult().getCode());
|
||||
return marshalWithLenientRetry(getErrorResponse(e.getResult(), trid));
|
||||
} catch (Throwable e) {
|
||||
// Something bad and unexpected happened. Send the client a generic error, and log it.
|
||||
logger.severe(e, "Unexpected failure");
|
||||
metrics.setEppStatus(Code.CommandFailed);
|
||||
return marshalWithLenientRetry(getErrorResponse(Result.create(Code.CommandFailed), trid));
|
||||
} finally {
|
||||
metrics.export();
|
||||
}
|
||||
}
|
||||
|
||||
/** Create a response indicating an Epp failure. */
|
||||
@VisibleForTesting
|
||||
static EppOutput getErrorResponse(Result result, Trid trid) {
|
||||
// Create TRID (without a clTRID) if one hasn't been created yet, as it's necessary to construct
|
||||
// a valid response. This can happen if the error occurred before we could even parse out the
|
||||
// clTRID (e.g. if a syntax error occurred parsing the supplied XML).
|
||||
return EppOutput.create(new Response.Builder()
|
||||
.setTrid(trid == null ? Trid.create(null) : trid)
|
||||
.setResult(result)
|
||||
.setExecutionTime(clock.nowUtc())
|
||||
.build());
|
||||
}
|
||||
}
|
240
java/google/registry/flows/EppException.java
Normal file
240
java/google/registry/flows/EppException.java
Normal file
|
@ -0,0 +1,240 @@
|
|||
// 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.flows;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.domain.registry.model.annotations.ExternalMessagingName;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.InnerCommand;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand;
|
||||
import com.google.domain.registry.model.eppoutput.Result;
|
||||
import com.google.domain.registry.model.eppoutput.Result.Code;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/** Exception used to propagate all failures containing one or more EPP responses. */
|
||||
public abstract class EppException extends Exception {
|
||||
|
||||
private final Result result;
|
||||
|
||||
/** Create an EppException with a custom message. */
|
||||
private EppException(String message) {
|
||||
super(message);
|
||||
Code code = getClass().getAnnotation(EppResultCode.class).value();
|
||||
Preconditions.checkState(!code.isSuccess());
|
||||
this.result = Result.create(code, message);
|
||||
}
|
||||
|
||||
/** Create an EppException with the default message for this code. */
|
||||
private EppException() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public Result getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Annotation for associating an EPP Result.Code value with an EppException subclass. */
|
||||
@Documented
|
||||
@Inherited
|
||||
@Retention(RUNTIME)
|
||||
@Target(TYPE)
|
||||
public @interface EppResultCode {
|
||||
/** The Code value associated with this exception. */
|
||||
Code value();
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.AuthenticationError)
|
||||
public abstract static class AuthenticationErrorException extends EppException {
|
||||
public AuthenticationErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.AuthenticationErrorClosingConnection)
|
||||
public abstract static class AuthenticationErrorClosingConnectionException extends EppException {
|
||||
public AuthenticationErrorClosingConnectionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.AuthorizationError)
|
||||
public abstract static class AuthorizationErrorException extends EppException {
|
||||
public AuthorizationErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.InvalidAuthorizationInformationError)
|
||||
public abstract static class InvalidAuthorizationInformationErrorException extends EppException {
|
||||
public InvalidAuthorizationInformationErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.CommandUseError)
|
||||
public abstract static class CommandUseErrorException extends EppException {
|
||||
public CommandUseErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.ObjectExists)
|
||||
public abstract static class ObjectAlreadyExistsException extends EppException {
|
||||
public ObjectAlreadyExistsException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.ObjectDoesNotExist)
|
||||
public abstract static class ObjectDoesNotExistException extends EppException {
|
||||
public ObjectDoesNotExistException(Class<?> type, String id) {
|
||||
super(
|
||||
String.format(
|
||||
"The %s with given ID (%s) doesn't exist.",
|
||||
!type.isAnnotationPresent(ExternalMessagingName.class)
|
||||
? "object"
|
||||
: type.getAnnotation(ExternalMessagingName.class).value(),
|
||||
id));
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.ObjectPendingTransfer)
|
||||
public abstract static class ObjectPendingTransferException extends EppException {
|
||||
public ObjectPendingTransferException(String id) {
|
||||
super(String.format("Object with given ID (%s) already has a pending transfer.", id));
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.ObjectNotPendingTransfer)
|
||||
public abstract static class ObjectNotPendingTransferException extends EppException {
|
||||
public ObjectNotPendingTransferException(String id) {
|
||||
super(String.format("Object with given ID (%s) does not have a pending transfer.", id));
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.AssociationProhibitsOperation)
|
||||
public abstract static class AssociationProhibitsOperationException extends EppException {
|
||||
public AssociationProhibitsOperationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.ParameterValuePolicyError)
|
||||
public abstract static class ParameterValuePolicyErrorException extends EppException {
|
||||
public ParameterValuePolicyErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.ParameterValueRangeError)
|
||||
public abstract static class ParameterValueRangeErrorException extends EppException {
|
||||
public ParameterValueRangeErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.ParameterValueSyntaxError)
|
||||
public abstract static class ParameterValueSyntaxErrorException extends EppException {
|
||||
public ParameterValueSyntaxErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.RequiredParameterMissing)
|
||||
public abstract static class RequiredParameterMissingException extends EppException {
|
||||
public RequiredParameterMissingException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.StatusProhibitsOperation)
|
||||
public abstract static class StatusProhibitsOperationException extends EppException {
|
||||
public StatusProhibitsOperationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.SyntaxError)
|
||||
public abstract static class SyntaxErrorException extends EppException {
|
||||
public SyntaxErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Specified command is not implemented. */
|
||||
@EppResultCode(Code.UnimplementedCommand)
|
||||
public static class UnimplementedCommandException extends EppException {
|
||||
public UnimplementedCommandException(InnerCommand command, ResourceCommand resourceCommand) {
|
||||
super(String.format(
|
||||
"No flow found for %s with extension %s",
|
||||
command.getClass().getSimpleName(),
|
||||
resourceCommand == null ? null : resourceCommand.getClass().getSimpleName()));
|
||||
}
|
||||
}
|
||||
|
||||
/** Abstract exception class. Do not throw this directly or catch in tests. */
|
||||
@EppResultCode(Code.UnimplementedOption)
|
||||
public abstract static class UnimplementedOptionException extends EppException {
|
||||
public UnimplementedOptionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
/** Specified extension is not implemented. */
|
||||
@EppResultCode(Code.UnimplementedExtension)
|
||||
public static class UnimplementedExtensionException extends EppException {
|
||||
public UnimplementedExtensionException() {
|
||||
super("Specified extension is not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
/** Specified object service is not implemented. */
|
||||
@EppResultCode(Code.UnimplementedObjectService)
|
||||
public static class UnimplementedObjectServiceException extends EppException {
|
||||
public UnimplementedObjectServiceException() {
|
||||
super("Specified object service is not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
/** Specified protocol version is not implemented. */
|
||||
@EppResultCode(Code.UnimplementedProtocolVersion)
|
||||
public static class UnimplementedProtocolVersionException extends EppException {
|
||||
public UnimplementedProtocolVersionException() {
|
||||
super("Specified protocol version is not implemented");
|
||||
}
|
||||
}
|
||||
}
|
67
java/google/registry/flows/EppServletUtils.java
Normal file
67
java/google/registry/flows/EppServletUtils.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.EppController.handleEppCommand;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
|
||||
import static javax.servlet.http.HttpServletResponse.SC_OK;
|
||||
|
||||
import com.google.common.net.MediaType;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/** Utility methods for Epp servlet classes. */
|
||||
public final class EppServletUtils {
|
||||
|
||||
public static final MediaType APPLICATION_EPP_XML_UTF8 =
|
||||
MediaType.create("application", "epp+xml").withCharset(UTF_8);
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
/**
|
||||
* Handle an EPP request and write out a servlet response.
|
||||
*
|
||||
* @throws IOException upon failure writing to {@code rsp}
|
||||
*/
|
||||
static void handleEppCommandAndWriteResponse(
|
||||
byte[] inputXmlBytes, HttpServletResponse rsp, SessionMetadata sessionMetadata)
|
||||
throws IOException {
|
||||
byte[] response;
|
||||
try {
|
||||
response = handleEppCommand(inputXmlBytes, sessionMetadata);
|
||||
} catch (Exception e) {
|
||||
logger.warning(e, "handleEppCommand general exception");
|
||||
rsp.setStatus(SC_BAD_REQUEST);
|
||||
return;
|
||||
}
|
||||
// Note that we always return 200 (OK) even if the EppController returns an error response.
|
||||
// This is because returning an non-OK HTTP status code will cause the proxy server to
|
||||
// silently close the connection without returning any data. The only time we will ever return
|
||||
// a non-OK status (400) is if we fail to muster even an EPP error response message. In that
|
||||
// case it's better to close the connection than to return garbage.
|
||||
rsp.setStatus(SC_OK);
|
||||
rsp.setContentType(APPLICATION_EPP_XML_UTF8.toString());
|
||||
try (OutputStream output = rsp.getOutputStream()) {
|
||||
output.write(response);
|
||||
}
|
||||
}
|
||||
|
||||
private EppServletUtils() {}
|
||||
}
|
72
java/google/registry/flows/EppTlsServlet.java
Normal file
72
java/google/registry/flows/EppTlsServlet.java
Normal 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.flows;
|
||||
|
||||
import static com.google.common.io.ByteStreams.toByteArray;
|
||||
import static com.google.domain.registry.flows.EppServletUtils.handleEppCommandAndWriteResponse;
|
||||
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* The {@link EppTlsServlet} class establishes a transport for EPP+TLS over* HTTP. All commands and
|
||||
* responses are EPP XML according to RFC 5730. Commands must must requested via POST.
|
||||
* <p>
|
||||
* There are a number of expected headers to this endpoint:
|
||||
* <dl>
|
||||
* <dt>{@value #SSL_CLIENT_CERTIFICATE_HASH_FIELD}
|
||||
* <dd>
|
||||
* This field should contain a base64 encoded digest of the client's TLS certificate. It is
|
||||
* validated during an EPP login command against a known good value that is transmitted out of
|
||||
* band.
|
||||
* <dt>{@value #FORWARDED_FOR_FIELD}
|
||||
* <dd>
|
||||
* This field should contain the host and port of the connecting client. It is validated during
|
||||
* an EPP login command against an IP whitelist that is transmitted out of band.
|
||||
* <dt>{@value #REQUESTED_SERVERNAME_VIA_SNI_FIELD}
|
||||
* <dd>
|
||||
* This field should contain the servername that the client requested during the TLS handshake.
|
||||
* It is unused, but expected to be present in the GFE-proxied configuration.
|
||||
* </dl>
|
||||
*/
|
||||
public class EppTlsServlet extends HttpServlet {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
static final String REQUESTED_SERVERNAME_VIA_SNI_FIELD = "X-GFE-Requested-Servername-SNI";
|
||||
static final String FORWARDED_FOR_FIELD = "X-Forwarded-For";
|
||||
static final String SSL_CLIENT_CERTIFICATE_HASH_FIELD = "X-GFE-SSL-Certificate";
|
||||
|
||||
@Override
|
||||
public void doPost(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
|
||||
// Check that SNI header is present. This is a signal that we're receiving traffic proxied by a
|
||||
// GFE, which is the expectation of this servlet. The value is unused.
|
||||
TlsCredentials tlsCredentials = new TlsCredentials(req);
|
||||
if (!tlsCredentials.hasSni()) {
|
||||
logger.warning("Request did not include required SNI header.");
|
||||
}
|
||||
SessionMetadata sessionMetadata = new HttpSessionMetadata(tlsCredentials, req.getSession(true));
|
||||
// Note that we are using the raw input stream rather than the reader, which implies that we are
|
||||
// ignoring the HTTP-specified charset (if any) in favor of whatever charset the XML declares.
|
||||
// This is ok because this code is only called from the proxy, which can't specify a charset
|
||||
// (it blindly copies bytes off a socket).
|
||||
handleEppCommandAndWriteResponse(toByteArray(req.getInputStream()), rsp, sessionMetadata);
|
||||
}
|
||||
}
|
53
java/google/registry/flows/EppToolServlet.java
Normal file
53
java/google/registry/flows/EppToolServlet.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.EppServletUtils.handleEppCommandAndWriteResponse;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.domain.registry.flows.SessionMetadata.SessionSource;
|
||||
import com.google.domain.registry.model.eppcommon.ProtocolDefinition;
|
||||
import com.google.domain.registry.security.XsrfProtectedServlet;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* This servlet runs EPP commands directly without logging in. It verifies an XSRF token that could
|
||||
* only come from the tool.
|
||||
*/
|
||||
public class EppToolServlet extends XsrfProtectedServlet {
|
||||
|
||||
/** Used to verify XSRF tokens. */
|
||||
public static final String XSRF_SCOPE = "admin";
|
||||
|
||||
public EppToolServlet() {
|
||||
super(XSRF_SCOPE, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doPost(HttpServletRequest req, HttpServletResponse rsp) throws IOException {
|
||||
byte[] xml = ByteStreams.toByteArray(req.getInputStream());
|
||||
handleEppCommandAndWriteResponse(
|
||||
xml, rsp, new StatelessRequestSessionMetadata(
|
||||
req.getParameter("clientIdentifier"),
|
||||
Boolean.parseBoolean(req.getParameter("superuser")),
|
||||
Boolean.parseBoolean(req.getParameter("dryRun")),
|
||||
ProtocolDefinition.getVisibleServiceExtensionUris(),
|
||||
SessionSource.TOOL));
|
||||
}
|
||||
}
|
170
java/google/registry/flows/EppXmlTransformer.java
Normal file
170
java/google/registry/flows/EppXmlTransformer.java
Normal file
|
@ -0,0 +1,170 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.domain.registry.xml.ValidationMode.LENIENT;
|
||||
import static com.google.domain.registry.xml.ValidationMode.STRICT;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import com.google.domain.registry.flows.EppException.SyntaxErrorException;
|
||||
import com.google.domain.registry.flows.EppException.UnimplementedProtocolVersionException;
|
||||
import com.google.domain.registry.model.EppResourceUtils.InvalidRepoIdException;
|
||||
import com.google.domain.registry.model.ImmutableObject;
|
||||
import com.google.domain.registry.model.eppinput.EppInput;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.WrongProtocolVersionException;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.host.InetAddressAdapter.IpVersionMismatchException;
|
||||
import com.google.domain.registry.model.translators.CurrencyUnitAdapter.UnknownCurrencyException;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
import com.google.domain.registry.xml.ValidationMode;
|
||||
import com.google.domain.registry.xml.XmlException;
|
||||
import com.google.domain.registry.xml.XmlTransformer;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
/** {@link XmlTransformer} for marshalling to and from the Epp model classes. */
|
||||
public class EppXmlTransformer {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
// Hardcoded XML schemas, ordered with respect to dependency.
|
||||
private static final ImmutableList<String> SCHEMAS = ImmutableList.of(
|
||||
"eppcom.xsd",
|
||||
"epp.xsd",
|
||||
"contact.xsd",
|
||||
"host.xsd",
|
||||
"domain.xsd",
|
||||
"rgp.xsd",
|
||||
"secdns.xsd",
|
||||
"fee.xsd",
|
||||
"metadata.xsd",
|
||||
"mark.xsd",
|
||||
"dsig.xsd",
|
||||
"smd.xsd",
|
||||
"launch.xsd",
|
||||
"allocate.xsd");
|
||||
|
||||
private static final XmlTransformer INPUT_TRANSFORMER =
|
||||
new XmlTransformer(SCHEMAS, EppInput.class);
|
||||
|
||||
private static final XmlTransformer OUTPUT_TRANSFORMER =
|
||||
new XmlTransformer(SCHEMAS, EppOutput.class);
|
||||
|
||||
public static void validateOutput(String xml) throws XmlException {
|
||||
OUTPUT_TRANSFORMER.validate(xml);
|
||||
}
|
||||
|
||||
public static <T> T unmarshal(byte[] bytes) throws EppException {
|
||||
try {
|
||||
return INPUT_TRANSFORMER.unmarshal(new ByteArrayInputStream(bytes));
|
||||
} catch (XmlException e) {
|
||||
// If this XmlException is wrapping a known type find it. If not, it's a syntax error.
|
||||
FluentIterable<Throwable> causalChain = FluentIterable.from(Throwables.getCausalChain(e));
|
||||
if (!(causalChain.filter(IpVersionMismatchException.class).isEmpty())) {
|
||||
throw new IpAddressVersionMismatchException();
|
||||
}
|
||||
if (!(causalChain.filter(WrongProtocolVersionException.class).isEmpty())) {
|
||||
throw new UnimplementedProtocolVersionException();
|
||||
}
|
||||
if (!(causalChain.filter(InvalidRepoIdException.class).isEmpty())) {
|
||||
throw new InvalidRepoIdEppException();
|
||||
}
|
||||
if (!(causalChain.filter(UnknownCurrencyException.class).isEmpty())) {
|
||||
throw new UnknownCurrencyEppException();
|
||||
}
|
||||
throw new GenericSyntaxErrorException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private static byte[] marshal(
|
||||
XmlTransformer transformer,
|
||||
ImmutableObject root,
|
||||
ValidationMode validation) throws XmlException {
|
||||
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
|
||||
transformer.marshal(root, byteArrayOutputStream, UTF_8, validation);
|
||||
return byteArrayOutputStream.toByteArray();
|
||||
}
|
||||
|
||||
public static byte[] marshal(EppOutput root, ValidationMode validation) throws XmlException {
|
||||
return marshal(OUTPUT_TRANSFORMER, root, validation);
|
||||
}
|
||||
|
||||
public static byte[] marshalWithLenientRetry(EppOutput eppOutput) {
|
||||
checkState(eppOutput != null);
|
||||
// We need to marshal to a string instead of writing the response directly to the servlet's
|
||||
// response writer, so that partial results don't get written on failure.
|
||||
try {
|
||||
return EppXmlTransformer.marshal(eppOutput, STRICT);
|
||||
} catch (XmlException e) {
|
||||
// We failed to marshal with validation. This is very bad, but we can potentially still send
|
||||
// back slightly invalid xml, so try again without validation.
|
||||
try {
|
||||
byte[] lenient = EppXmlTransformer.marshal(eppOutput, LENIENT);
|
||||
// Marshaling worked even though the results didn't validate against the schema.
|
||||
logger.severe(e, "Result marshaled but did not validate: " + new String(lenient, UTF_8));
|
||||
return lenient;
|
||||
} catch (XmlException e2) {
|
||||
throw new RuntimeException(e2); // Failing to marshal at all is not recoverable.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static byte[] marshalInput(EppInput root, ValidationMode validation) throws XmlException {
|
||||
return marshal(INPUT_TRANSFORMER, root, validation);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void validateInput(String xml) throws XmlException {
|
||||
INPUT_TRANSFORMER.validate(xml);
|
||||
}
|
||||
|
||||
/** IP address version mismatch. */
|
||||
public static class IpAddressVersionMismatchException extends ParameterValueRangeErrorException {
|
||||
public IpAddressVersionMismatchException() {
|
||||
super("IP adddress version mismatch");
|
||||
}
|
||||
}
|
||||
|
||||
/** Invalid format for repository id. */
|
||||
public static class InvalidRepoIdEppException extends ParameterValueSyntaxErrorException {
|
||||
public InvalidRepoIdEppException() {
|
||||
super("Invalid format for repository id");
|
||||
}
|
||||
}
|
||||
|
||||
/** Unknown currency. */
|
||||
static class UnknownCurrencyEppException extends ParameterValueRangeErrorException {
|
||||
public UnknownCurrencyEppException() {
|
||||
super("Unknown currency.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Generic syntax error that can be thrown by any flow. */
|
||||
static class GenericSyntaxErrorException extends SyntaxErrorException {
|
||||
public GenericSyntaxErrorException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
130
java/google/registry/flows/Flow.java
Normal file
130
java/google/registry/flows/Flow.java
Normal file
|
@ -0,0 +1,130 @@
|
|||
// 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.flows;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.model.eppcommon.Trid;
|
||||
import com.google.domain.registry.model.eppinput.EppInput;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Response;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseData;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
import com.google.domain.registry.model.eppoutput.Result;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An abstract EPP flow.
|
||||
* <p>
|
||||
* This class also contains static methods for loading an appropriate flow based on model classes.
|
||||
*/
|
||||
public abstract class Flow {
|
||||
|
||||
protected EppInput eppInput;
|
||||
protected SessionMetadata sessionMetadata;
|
||||
protected Trid trid;
|
||||
protected DateTime now;
|
||||
protected byte[] inputXmlBytes;
|
||||
|
||||
/** Whether this flow is being run in a superuser mode that can skip some checks. */
|
||||
protected boolean superuser;
|
||||
|
||||
/** The collection of allowed extensions for the flow. */
|
||||
private Set<Class<? extends CommandExtension>> validExtensions = new HashSet<>();
|
||||
|
||||
/** Flows can override this for custom initialization. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void initFlow() throws EppException {}
|
||||
|
||||
/** Execute the business logic for this flow. */
|
||||
protected abstract EppOutput run() throws EppException;
|
||||
|
||||
/**
|
||||
* Subclasses that create a resource should override this to return the repoId of the new
|
||||
* resource.
|
||||
*/
|
||||
protected String getCreatedRepoId() {
|
||||
return null;
|
||||
}
|
||||
|
||||
protected String getClientId() {
|
||||
return sessionMetadata.getClientId();
|
||||
}
|
||||
|
||||
protected EppOutput createOutput(Result.Code code) {
|
||||
return createOutput(code, null);
|
||||
}
|
||||
|
||||
protected EppOutput createOutput(Result.Code code, ResponseData responseData) {
|
||||
return createOutput(code, responseData, null);
|
||||
}
|
||||
|
||||
protected EppOutput createOutput(
|
||||
Result.Code code,
|
||||
ResponseData responseData,
|
||||
ImmutableList<? extends ResponseExtension> extensions) {
|
||||
return EppOutput.create(new Response.Builder()
|
||||
.setTrid(trid)
|
||||
.setResult(Result.create(code))
|
||||
.setExecutionTime(now)
|
||||
.setCreatedRepoId(getCreatedRepoId())
|
||||
.setResData(responseData == null ? null : ImmutableList.of(responseData))
|
||||
.setExtensions(extensions)
|
||||
.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Using an init function instead of a constructor avoids duplicating constructors across the
|
||||
* entire hierarchy of flow classes
|
||||
*/
|
||||
public final Flow init(
|
||||
EppInput eppInput,
|
||||
Trid trid,
|
||||
SessionMetadata sessionMetadata,
|
||||
boolean superuser,
|
||||
DateTime now,
|
||||
byte[] inputXmlBytes) throws EppException {
|
||||
this.eppInput = eppInput;
|
||||
this.trid = trid;
|
||||
this.sessionMetadata = sessionMetadata;
|
||||
this.now = now;
|
||||
this.superuser = superuser;
|
||||
this.inputXmlBytes = inputXmlBytes;
|
||||
initFlow();
|
||||
validExtensions = ImmutableSet.copyOf(validExtensions);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an extension class as a valid extension for a flow.
|
||||
* Must be called in the init series of methods, as the validExtensions
|
||||
* becomes immutable once init is complete.
|
||||
*/
|
||||
@SafeVarargs
|
||||
protected final void registerExtensions(Class<? extends CommandExtension>... extensions) {
|
||||
Collections.addAll(validExtensions, extensions);
|
||||
}
|
||||
|
||||
/** Get the legal command extension types for this flow. */
|
||||
protected final Set<Class<? extends CommandExtension>> getValidRequestExtensions() {
|
||||
return ImmutableSet.copyOf(validExtensions);
|
||||
}
|
||||
}
|
334
java/google/registry/flows/FlowRegistry.java
Normal file
334
java/google/registry/flows/FlowRegistry.java
Normal file
|
@ -0,0 +1,334 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.domain.registry.model.domain.launch.LaunchCreateExtension.CreateType.APPLICATION;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.ImmutableTable;
|
||||
import com.google.common.collect.Table;
|
||||
import com.google.domain.registry.flows.EppException.SyntaxErrorException;
|
||||
import com.google.domain.registry.flows.EppException.UnimplementedCommandException;
|
||||
import com.google.domain.registry.flows.contact.ContactCheckFlow;
|
||||
import com.google.domain.registry.flows.contact.ContactCreateFlow;
|
||||
import com.google.domain.registry.flows.contact.ContactDeleteFlow;
|
||||
import com.google.domain.registry.flows.contact.ContactInfoFlow;
|
||||
import com.google.domain.registry.flows.contact.ContactTransferApproveFlow;
|
||||
import com.google.domain.registry.flows.contact.ContactTransferCancelFlow;
|
||||
import com.google.domain.registry.flows.contact.ContactTransferQueryFlow;
|
||||
import com.google.domain.registry.flows.contact.ContactTransferRejectFlow;
|
||||
import com.google.domain.registry.flows.contact.ContactTransferRequestFlow;
|
||||
import com.google.domain.registry.flows.contact.ContactUpdateFlow;
|
||||
import com.google.domain.registry.flows.domain.ClaimsCheckFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainAllocateFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainApplicationCreateFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainApplicationDeleteFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainApplicationInfoFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainApplicationUpdateFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainCheckFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainCreateFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainDeleteFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainInfoFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainRenewFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainRestoreRequestFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainTransferApproveFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainTransferCancelFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainTransferQueryFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainTransferRejectFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainTransferRequestFlow;
|
||||
import com.google.domain.registry.flows.domain.DomainUpdateFlow;
|
||||
import com.google.domain.registry.flows.host.HostCheckFlow;
|
||||
import com.google.domain.registry.flows.host.HostCreateFlow;
|
||||
import com.google.domain.registry.flows.host.HostDeleteFlow;
|
||||
import com.google.domain.registry.flows.host.HostInfoFlow;
|
||||
import com.google.domain.registry.flows.host.HostUpdateFlow;
|
||||
import com.google.domain.registry.flows.poll.PollAckFlow;
|
||||
import com.google.domain.registry.flows.poll.PollRequestFlow;
|
||||
import com.google.domain.registry.flows.session.HelloFlow;
|
||||
import com.google.domain.registry.flows.session.LoginFlow;
|
||||
import com.google.domain.registry.flows.session.LogoutFlow;
|
||||
import com.google.domain.registry.model.contact.ContactCommand;
|
||||
import com.google.domain.registry.model.domain.DomainCommand;
|
||||
import com.google.domain.registry.model.domain.allocate.AllocateCreateExtension;
|
||||
import com.google.domain.registry.model.domain.launch.ApplicationIdTargetExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCheckExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCheckExtension.CheckType;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCreateExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchPhase;
|
||||
import com.google.domain.registry.model.domain.rgp.RestoreCommand.RestoreOp;
|
||||
import com.google.domain.registry.model.domain.rgp.RgpUpdateExtension;
|
||||
import com.google.domain.registry.model.eppinput.EppInput;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.Hello;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.InnerCommand;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.Login;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.Logout;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.Poll;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.ResourceCommandWrapper;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.Transfer;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.Transfer.TransferOp;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand;
|
||||
import com.google.domain.registry.model.host.HostCommand;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** Registry that can select a flow to handle a given Epp command. */
|
||||
public class FlowRegistry {
|
||||
|
||||
/** Marker class for unimplemented flows. */
|
||||
private abstract static class UnimplementedFlow extends Flow {}
|
||||
|
||||
/** A function type that takes an {@link EppInput} and returns a {@link Flow} class. */
|
||||
private abstract static class FlowProvider {
|
||||
/** Get the flow associated with this {@link EppInput} or return null to signal no match. */
|
||||
Class<? extends Flow> get(EppInput eppInput) {
|
||||
InnerCommand innerCommand = eppInput.getCommandWrapper().getCommand();
|
||||
return get(eppInput, innerCommand, (innerCommand instanceof ResourceCommandWrapper)
|
||||
? ((ResourceCommandWrapper) innerCommand).getResourceCommand() : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses need to implement this to examine the parameters and choose a flow (or null if
|
||||
* the subclass doesn't know of an appropriate flow.
|
||||
*/
|
||||
abstract Class<? extends Flow> get(
|
||||
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand);
|
||||
}
|
||||
|
||||
/** The hello flow is keyed on a special {@code CommandWrapper} type. */
|
||||
private static final FlowProvider HELLO_FLOW_PROVIDER = new FlowProvider() {
|
||||
@Override
|
||||
Class<? extends Flow> get(
|
||||
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
|
||||
return eppInput.getCommandWrapper() instanceof Hello ? HelloFlow.class : null;
|
||||
}};
|
||||
|
||||
/** Session flows like login and logout are keyed only on the {@link InnerCommand} type. */
|
||||
private static final FlowProvider SESSION_FLOW_PROVIDER = new FlowProvider() {
|
||||
private final Map<Class<?>, Class<? extends Flow>> commandFlows =
|
||||
ImmutableMap.<Class<?>, Class<? extends Flow>>of(
|
||||
Login.class, LoginFlow.class,
|
||||
Logout.class, LogoutFlow.class);
|
||||
|
||||
@Override
|
||||
Class<? extends Flow> get(
|
||||
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
|
||||
return innerCommand == null ? null : commandFlows.get(innerCommand.getClass());
|
||||
}};
|
||||
|
||||
/** Poll flows have an {@link InnerCommand} of type {@link Poll}. */
|
||||
private static final FlowProvider POLL_FLOW_PROVIDER = new FlowProvider() {
|
||||
@Override
|
||||
Class<? extends Flow> get(
|
||||
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
|
||||
if (!(innerCommand instanceof Poll)) {
|
||||
return null;
|
||||
}
|
||||
switch (((Poll) innerCommand).getPollOp()) {
|
||||
case ACK:
|
||||
return PollAckFlow.class;
|
||||
case REQUEST:
|
||||
return PollRequestFlow.class;
|
||||
default:
|
||||
return UnimplementedFlow.class;
|
||||
}
|
||||
}};
|
||||
|
||||
/**
|
||||
* The domain restore command is technically a domain {@literal <update>}, but logically a totally
|
||||
* separate flow.
|
||||
* <p>
|
||||
* This provider must be tried before {@link #RESOURCE_CRUD_FLOW_PROVIDER}. Otherwise, the regular
|
||||
* domain update flow will match first.
|
||||
*/
|
||||
private static final FlowProvider DOMAIN_RESTORE_FLOW_PROVIDER = new FlowProvider() {
|
||||
@Override
|
||||
Class<? extends Flow> get(
|
||||
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
|
||||
if (!(resourceCommand instanceof DomainCommand.Update)) {
|
||||
return null;
|
||||
}
|
||||
RgpUpdateExtension rgpUpdateExtension = eppInput.getSingleExtension(RgpUpdateExtension.class);
|
||||
if (rgpUpdateExtension == null) {
|
||||
return null;
|
||||
}
|
||||
// Restore command with an op of "report" is not currently supported.
|
||||
return (rgpUpdateExtension.getRestoreCommand().getRestoreOp() == RestoreOp.REQUEST)
|
||||
? DomainRestoreRequestFlow.class
|
||||
: UnimplementedFlow.class;
|
||||
}};
|
||||
|
||||
/**
|
||||
* The claims check flow is keyed on the type of the {@link ResourceCommand} and on having the
|
||||
* correct extension with a specific phase value.
|
||||
*/
|
||||
private static final FlowProvider DOMAIN_CHECK_FLOW_PROVIDER = new FlowProvider() {
|
||||
@Override
|
||||
Class<? extends Flow> get(
|
||||
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
|
||||
if (!(resourceCommand instanceof DomainCommand.Check)) {
|
||||
return null;
|
||||
}
|
||||
LaunchCheckExtension extension = eppInput.getSingleExtension(LaunchCheckExtension.class);
|
||||
if (extension == null || CheckType.AVAILABILITY.equals(extension.getCheckType())) {
|
||||
// We don't distinguish between registry phases for "avail", so don't bother checking phase.
|
||||
return DomainCheckFlow.class;
|
||||
}
|
||||
if (CheckType.CLAIMS.equals(extension.getCheckType())
|
||||
&& LaunchPhase.CLAIMS.equals(extension.getPhase())) {
|
||||
return ClaimsCheckFlow.class;
|
||||
}
|
||||
return null;
|
||||
}};
|
||||
|
||||
/** General resource CRUD flows are keyed on the type of their {@link ResourceCommand}. */
|
||||
private static final FlowProvider RESOURCE_CRUD_FLOW_PROVIDER = new FlowProvider() {
|
||||
private final Map<Class<?>, Class<? extends Flow>> resourceCrudFlows =
|
||||
new ImmutableMap.Builder<Class<?>, Class<? extends Flow>>()
|
||||
.put(ContactCommand.Check.class, ContactCheckFlow.class)
|
||||
.put(ContactCommand.Create.class, ContactCreateFlow.class)
|
||||
.put(ContactCommand.Delete.class, ContactDeleteFlow.class)
|
||||
.put(ContactCommand.Info.class, ContactInfoFlow.class)
|
||||
.put(ContactCommand.Update.class, ContactUpdateFlow.class)
|
||||
.put(DomainCommand.Create.class, DomainCreateFlow.class)
|
||||
.put(DomainCommand.Delete.class, DomainDeleteFlow.class)
|
||||
.put(DomainCommand.Info.class, DomainInfoFlow.class)
|
||||
.put(DomainCommand.Renew.class, DomainRenewFlow.class)
|
||||
.put(DomainCommand.Update.class, DomainUpdateFlow.class)
|
||||
.put(HostCommand.Check.class, HostCheckFlow.class)
|
||||
.put(HostCommand.Create.class, HostCreateFlow.class)
|
||||
.put(HostCommand.Delete.class, HostDeleteFlow.class)
|
||||
.put(HostCommand.Info.class, HostInfoFlow.class)
|
||||
.put(HostCommand.Update.class, HostUpdateFlow.class)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
Class<? extends Flow> get(
|
||||
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
|
||||
return resourceCommand == null ? null : resourceCrudFlows.get(resourceCommand.getClass());
|
||||
}};
|
||||
|
||||
/** The domain allocate flow has a specific extension. */
|
||||
private static final FlowProvider ALLOCATE_FLOW_PROVIDER = new FlowProvider() {
|
||||
@Override
|
||||
Class<? extends Flow> get(
|
||||
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
|
||||
return (resourceCommand instanceof DomainCommand.Create
|
||||
&& eppInput.getSingleExtension(AllocateCreateExtension.class) != null)
|
||||
? DomainAllocateFlow.class : null;
|
||||
}};
|
||||
|
||||
/**
|
||||
* Application CRUD flows have an extension and are keyed on the type of their
|
||||
* {@link ResourceCommand}.
|
||||
*/
|
||||
private static final FlowProvider APPLICATION_CRUD_FLOW_PROVIDER = new FlowProvider() {
|
||||
|
||||
private final Map<Class<? extends ResourceCommand>, Class<? extends Flow>> applicationFlows =
|
||||
ImmutableMap.<Class<? extends ResourceCommand>, Class<? extends Flow>>of(
|
||||
DomainCommand.Create.class, DomainApplicationCreateFlow.class,
|
||||
DomainCommand.Delete.class, DomainApplicationDeleteFlow.class,
|
||||
DomainCommand.Info.class, DomainApplicationInfoFlow.class,
|
||||
DomainCommand.Update.class, DomainApplicationUpdateFlow.class);
|
||||
|
||||
private final Set<LaunchPhase> launchPhases = ImmutableSet.of(
|
||||
LaunchPhase.SUNRISE, LaunchPhase.SUNRUSH, LaunchPhase.LANDRUSH);
|
||||
|
||||
@Override
|
||||
Class<? extends Flow> get(
|
||||
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
|
||||
if (eppInput.getSingleExtension(ApplicationIdTargetExtension.class) != null) {
|
||||
return applicationFlows.get(resourceCommand.getClass());
|
||||
}
|
||||
LaunchCreateExtension createExtension =
|
||||
eppInput.getSingleExtension(LaunchCreateExtension.class);
|
||||
// Return a flow if the type is APPLICATION, or if it's null and we are in a launch phase.
|
||||
// If the type is specified as REGISTRATION, return null.
|
||||
if (createExtension != null) {
|
||||
LaunchPhase launchPhase = createExtension.getPhase();
|
||||
if (APPLICATION.equals(createExtension.getCreateType())
|
||||
|| (createExtension.getCreateType() == null && launchPhases.contains(launchPhase))) {
|
||||
return applicationFlows.get(resourceCommand.getClass());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}};
|
||||
|
||||
/** Transfer flows have an {@link InnerCommand} of type {@link Transfer}. */
|
||||
private static final FlowProvider TRANSFER_FLOW_PROVIDER = new FlowProvider() {
|
||||
private final Table<Class<?>, TransferOp, Class<? extends Flow>> transferFlows = ImmutableTable
|
||||
.<Class<?>, TransferOp, Class<? extends Flow>>builder()
|
||||
.put(ContactCommand.Transfer.class, TransferOp.APPROVE, ContactTransferApproveFlow.class)
|
||||
.put(ContactCommand.Transfer.class, TransferOp.CANCEL, ContactTransferCancelFlow.class)
|
||||
.put(ContactCommand.Transfer.class, TransferOp.QUERY, ContactTransferQueryFlow.class)
|
||||
.put(ContactCommand.Transfer.class, TransferOp.REJECT, ContactTransferRejectFlow.class)
|
||||
.put(ContactCommand.Transfer.class, TransferOp.REQUEST, ContactTransferRequestFlow.class)
|
||||
.put(DomainCommand.Transfer.class, TransferOp.APPROVE, DomainTransferApproveFlow.class)
|
||||
.put(DomainCommand.Transfer.class, TransferOp.CANCEL, DomainTransferCancelFlow.class)
|
||||
.put(DomainCommand.Transfer.class, TransferOp.QUERY, DomainTransferQueryFlow.class)
|
||||
.put(DomainCommand.Transfer.class, TransferOp.REJECT, DomainTransferRejectFlow.class)
|
||||
.put(DomainCommand.Transfer.class, TransferOp.REQUEST, DomainTransferRequestFlow.class)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
Class<? extends Flow> get(
|
||||
EppInput eppInput, InnerCommand innerCommand, ResourceCommand resourceCommand) {
|
||||
return resourceCommand != null && innerCommand instanceof Transfer
|
||||
? transferFlows.get(resourceCommand.getClass(), ((Transfer) innerCommand).getTransferOp())
|
||||
: null;
|
||||
}};
|
||||
|
||||
/** Return the appropriate flow to handle this EPP command. */
|
||||
public static Class<? extends Flow> getFlowClass(EppInput eppInput) throws EppException {
|
||||
// Do some sanity checking on the input; anything but Hello must have a command type.
|
||||
InnerCommand innerCommand = eppInput.getCommandWrapper().getCommand();
|
||||
if (innerCommand == null && !(eppInput.getCommandWrapper() instanceof Hello)) {
|
||||
throw new MissingCommandException();
|
||||
}
|
||||
// Try the FlowProviders until we find a match. The order matters because it's possible to
|
||||
// match multiple FlowProviders and so more specific matches are tried first.
|
||||
for (FlowProvider flowProvider : new FlowProvider[] {
|
||||
HELLO_FLOW_PROVIDER,
|
||||
SESSION_FLOW_PROVIDER,
|
||||
POLL_FLOW_PROVIDER,
|
||||
DOMAIN_RESTORE_FLOW_PROVIDER,
|
||||
ALLOCATE_FLOW_PROVIDER,
|
||||
APPLICATION_CRUD_FLOW_PROVIDER,
|
||||
DOMAIN_CHECK_FLOW_PROVIDER,
|
||||
RESOURCE_CRUD_FLOW_PROVIDER,
|
||||
TRANSFER_FLOW_PROVIDER}) {
|
||||
Class<? extends Flow> flowClass = flowProvider.get(eppInput);
|
||||
if (flowClass == UnimplementedFlow.class) {
|
||||
break; // We found it, but it's marked as not implemented.
|
||||
}
|
||||
if (flowClass != null) {
|
||||
return flowClass; // We found it!
|
||||
}
|
||||
}
|
||||
// Nothing usable was found, so throw an exception.
|
||||
throw new UnimplementedCommandException(
|
||||
innerCommand,
|
||||
innerCommand instanceof ResourceCommandWrapper
|
||||
? ((ResourceCommandWrapper) innerCommand).getResourceCommand() : null);
|
||||
}
|
||||
|
||||
/** Command missing. */
|
||||
static class MissingCommandException extends SyntaxErrorException {
|
||||
public MissingCommandException() {
|
||||
super("Command missing");
|
||||
}
|
||||
}
|
||||
}
|
191
java/google/registry/flows/FlowRunner.java
Normal file
191
java/google/registry/flows/FlowRunner.java
Normal file
|
@ -0,0 +1,191 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.common.base.Throwables.getStackTraceAsString;
|
||||
import static com.google.common.base.Throwables.propagateIfInstanceOf;
|
||||
import static com.google.common.io.BaseEncoding.base64;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.xml.XmlTransformer.prettyPrint;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.domain.registry.model.eppcommon.Trid;
|
||||
import com.google.domain.registry.model.eppinput.EppInput;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.monitoring.whitebox.EppMetrics;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
import com.google.domain.registry.util.NonFinalForTesting;
|
||||
import com.google.domain.registry.util.SystemClock;
|
||||
import com.google.domain.registry.util.TypeUtils;
|
||||
|
||||
import com.googlecode.objectify.Work;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Run a flow, either transactionally or not, with logging and retrying as needed. */
|
||||
public class FlowRunner {
|
||||
|
||||
private static final String COMMAND_LOG_FORMAT = "EPP Command" + Strings.repeat("\n\t%s", 4);
|
||||
|
||||
/** Whether to actually write to the datastore or just simulate. */
|
||||
public enum CommitMode { LIVE, DRY_RUN }
|
||||
|
||||
/** Whether to run in normal or superuser mode. */
|
||||
public enum UserPrivileges { NORMAL, SUPERUSER }
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
@NonFinalForTesting
|
||||
private static Clock clock = new SystemClock();
|
||||
|
||||
private final Class<? extends Flow> flowClass;
|
||||
private final EppInput eppInput;
|
||||
private final Trid trid;
|
||||
private final SessionMetadata sessionMetadata;
|
||||
private final byte[] inputXmlBytes;
|
||||
private final EppMetrics metrics;
|
||||
|
||||
public FlowRunner(
|
||||
Class<? extends Flow> flowClass,
|
||||
EppInput eppInput,
|
||||
Trid trid,
|
||||
SessionMetadata sessionMetadata,
|
||||
byte[] inputXmlBytes,
|
||||
final EppMetrics metrics) {
|
||||
this.flowClass = flowClass;
|
||||
this.eppInput = eppInput;
|
||||
this.trid = trid;
|
||||
this.sessionMetadata = sessionMetadata;
|
||||
this.inputXmlBytes = inputXmlBytes;
|
||||
this.metrics = metrics;
|
||||
}
|
||||
|
||||
public EppOutput run(
|
||||
final CommitMode commitMode, final UserPrivileges userPrivileges) throws EppException {
|
||||
String clientId = sessionMetadata.getClientId();
|
||||
final boolean isSuperuser = UserPrivileges.SUPERUSER.equals(userPrivileges);
|
||||
logger.infofmt(
|
||||
COMMAND_LOG_FORMAT,
|
||||
trid.getServerTransactionId(),
|
||||
clientId,
|
||||
sessionMetadata,
|
||||
prettyPrint(inputXmlBytes).replaceAll("\n", "\n\t"));
|
||||
if (!isTransactional()) {
|
||||
if (metrics != null) {
|
||||
metrics.incrementAttempts();
|
||||
}
|
||||
return createAndInitFlow(isSuperuser, clock.nowUtc()).run();
|
||||
}
|
||||
// We log the command in a structured format. Note that we do this before the transaction;
|
||||
// if we did it after, we might miss a transaction that committed successfully but then crashed
|
||||
// before it could log.
|
||||
logger.info("EPP_Mutation " + new JsonLogStatement(trid)
|
||||
.add("client", clientId)
|
||||
.add("privileges", userPrivileges.toString())
|
||||
.add("xmlBytes", base64().encode(inputXmlBytes)));
|
||||
try {
|
||||
EppOutput flowResult = ofy().transact(new Work<EppOutput>() {
|
||||
@Override
|
||||
public EppOutput run() {
|
||||
if (metrics != null) {
|
||||
metrics.incrementAttempts();
|
||||
}
|
||||
try {
|
||||
EppOutput output = createAndInitFlow(isSuperuser, ofy().getTransactionTime()).run();
|
||||
if (CommitMode.DRY_RUN.equals(commitMode)) {
|
||||
throw new DryRunException(output);
|
||||
}
|
||||
return output;
|
||||
} catch (EppException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}});
|
||||
logger.info("EPP_Mutation_Committed " + new JsonLogStatement(trid)
|
||||
.add("createdRepoId", flowResult.getResponse().getCreatedRepoId())
|
||||
.add("executionTime", flowResult.getResponse().getExecutionTime().getMillis()));
|
||||
return flowResult;
|
||||
} catch (DryRunException e) {
|
||||
return e.output;
|
||||
} catch (RuntimeException e) {
|
||||
logger.warning("EPP_Mutation_Failed " + new JsonLogStatement(trid));
|
||||
logger.warning(getStackTraceAsString(e));
|
||||
propagateIfInstanceOf(e.getCause(), EppException.class);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private Flow createAndInitFlow(boolean superuser, DateTime now) throws EppException {
|
||||
return TypeUtils.<Flow>instantiate(flowClass).init(
|
||||
eppInput,
|
||||
trid,
|
||||
sessionMetadata,
|
||||
superuser,
|
||||
now,
|
||||
inputXmlBytes);
|
||||
}
|
||||
|
||||
public boolean isTransactional() {
|
||||
return TransactionalFlow.class.isAssignableFrom(flowClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper for logging in json format.
|
||||
* <p>
|
||||
* This is needed because the usual json outputters perform normalizations that we don't want or
|
||||
* need, since we know that our values never need to be escaped - there are only strings and
|
||||
* numbers, and the strings are not allowed to contain quote characters.
|
||||
* <p>
|
||||
* An example output for an EPP_Mutation:
|
||||
* {"trid":"abc-123", "client":"some_registrar", "tld":"com", "xmlBytes":"abc123DEF"}
|
||||
* <p>
|
||||
* An example output for an EPP_Mutation_Committed that doesn't create a new resource:
|
||||
* {"trid":"abc-123", "executionTime":123456789}
|
||||
* <p>
|
||||
* An example output for an EPP_Mutation_Committed that creates a new resource:
|
||||
* {"trid":"abc-123", "executionRepoId":123, "executionTime":123456789}
|
||||
*/
|
||||
private static class JsonLogStatement {
|
||||
|
||||
StringBuilder message;
|
||||
|
||||
JsonLogStatement(Trid trid) {
|
||||
message =
|
||||
new StringBuilder("{\"trid\":\"").append(trid.getServerTransactionId()).append('\"');
|
||||
}
|
||||
|
||||
JsonLogStatement add(String key, Object value) {
|
||||
if (value != null) {
|
||||
String quote = value instanceof String ? "\"" : "";
|
||||
message.append(String.format(", \"%s\":%s%s%s", key, quote, value, quote));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return message + "}";
|
||||
}
|
||||
}
|
||||
|
||||
/** Exception for canceling a transaction while capturing what the output would have been. */
|
||||
private class DryRunException extends RuntimeException {
|
||||
final EppOutput output;
|
||||
|
||||
DryRunException(EppOutput output) {
|
||||
this.output = output;
|
||||
}
|
||||
}
|
||||
}
|
61
java/google/registry/flows/HttpSessionMetadata.java
Normal file
61
java/google/registry/flows/HttpSessionMetadata.java
Normal file
|
@ -0,0 +1,61 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
/** A metadata class that is a wrapper around {@link HttpSession}. */
|
||||
public class HttpSessionMetadata extends SessionMetadata {
|
||||
|
||||
private final HttpSession session;
|
||||
private boolean isValid = true;
|
||||
|
||||
public HttpSessionMetadata(TransportCredentials credentials, HttpSession session) {
|
||||
this.session = session;
|
||||
setTransportCredentials(credentials);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void checkValid() {
|
||||
checkState(isValid, "This session has been invalidated.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
session.invalidate();
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setProperty(String key, Object value) {
|
||||
if (value == null) {
|
||||
session.removeAttribute(key);
|
||||
} else {
|
||||
session.setAttribute(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getProperty(String key) {
|
||||
return session.getAttribute(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionSource getSessionSource() {
|
||||
return SessionSource.HTTP;
|
||||
}
|
||||
}
|
135
java/google/registry/flows/LoggedInFlow.java
Normal file
135
java/google/registry/flows/LoggedInFlow.java
Normal file
|
@ -0,0 +1,135 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.common.base.Verify.verifyNotNull;
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.common.collect.Sets.intersection;
|
||||
import static com.google.domain.registry.model.registry.Registries.getTlds;
|
||||
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException.CommandUseErrorException;
|
||||
import com.google.domain.registry.flows.EppException.SyntaxErrorException;
|
||||
import com.google.domain.registry.flows.EppException.UnimplementedExtensionException;
|
||||
import com.google.domain.registry.model.eppcommon.ProtocolDefinition;
|
||||
import com.google.domain.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.CommandExtension;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/** A flow that requires being logged in. */
|
||||
public abstract class LoggedInFlow extends Flow {
|
||||
|
||||
static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
/**
|
||||
* A blacklist of service extension URIs that will cause an error if they are used without being
|
||||
* declared on login.
|
||||
*/
|
||||
private static final ImmutableSet<String> UNDECLARED_URIS_BLACKLIST =
|
||||
ImmutableSet.of(ServiceExtension.FEE_0_6.getUri());
|
||||
|
||||
/**
|
||||
* The TLDs on which the logged-in registrar is allowed access domains.
|
||||
*/
|
||||
private ImmutableSet<String> allowedTlds;
|
||||
|
||||
protected ImmutableSet<String> getAllowedTlds() {
|
||||
return allowedTlds;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void initFlow() throws EppException {
|
||||
if (getClientId() == null) {
|
||||
throw new NotLoggedInException();
|
||||
}
|
||||
// Validate that the extensions in the input match what this flow expects.
|
||||
ImmutableSet<Class<? extends CommandExtension>> extensionClasses = FluentIterable
|
||||
.from(eppInput.getCommandWrapper().getExtensions())
|
||||
.transform(new Function<CommandExtension, Class<? extends CommandExtension>>() {
|
||||
@Override
|
||||
public Class<? extends CommandExtension> apply(CommandExtension extension) {
|
||||
return extension.getClass();
|
||||
}})
|
||||
.toSet();
|
||||
if (extensionClasses.size() != eppInput.getCommandWrapper().getExtensions().size()) {
|
||||
throw new UnsupportedRepeatedExtensionException();
|
||||
}
|
||||
// Validate that we did not receive any undeclared extensions.
|
||||
ImmutableSet<String> extensionUris = FluentIterable
|
||||
.from(extensionClasses)
|
||||
.transform(new Function<Class<? extends CommandExtension>, String>() {
|
||||
@Override
|
||||
public String apply(Class<? extends CommandExtension> clazz) {
|
||||
return ProtocolDefinition.ServiceExtension.getCommandExtensionUri(clazz);
|
||||
}})
|
||||
.toSet();
|
||||
Set<String> undeclaredUris = difference(
|
||||
extensionUris, nullToEmpty(sessionMetadata.getServiceExtensionUris()));
|
||||
if (!undeclaredUris.isEmpty()) {
|
||||
Set<String> undeclaredUrisThatError = intersection(undeclaredUris, UNDECLARED_URIS_BLACKLIST);
|
||||
if (!undeclaredUrisThatError.isEmpty()) {
|
||||
throw new UndeclaredServiceExtensionException(undeclaredUrisThatError);
|
||||
} else {
|
||||
logger.warningfmt(
|
||||
"Client (%s) is attempting to run flow (%s) without declaring URIs %s on login",
|
||||
getClientId(), getClass().getSimpleName(), undeclaredUris);
|
||||
}
|
||||
}
|
||||
if (sessionMetadata.isSuperuser()) {
|
||||
allowedTlds = getTlds();
|
||||
} else {
|
||||
Registrar registrar = verifyNotNull(
|
||||
Registrar.loadByClientId(sessionMetadata.getClientId()),
|
||||
"Could not load registrar %s", sessionMetadata.getClientId());
|
||||
allowedTlds = registrar.getAllowedTlds();
|
||||
}
|
||||
initLoggedInFlow();
|
||||
if (!difference(extensionClasses, getValidRequestExtensions()).isEmpty()) {
|
||||
throw new UnimplementedExtensionException();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void initLoggedInFlow() throws EppException {}
|
||||
|
||||
/** Registrar is not logged in. */
|
||||
public static class NotLoggedInException extends CommandUseErrorException {
|
||||
public NotLoggedInException() {
|
||||
super("Registrar is not logged in.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Unsupported repetition of an extension. */
|
||||
static class UnsupportedRepeatedExtensionException extends SyntaxErrorException {
|
||||
public UnsupportedRepeatedExtensionException() {
|
||||
super("Unsupported repetition of an extension");
|
||||
}
|
||||
}
|
||||
|
||||
/** Service extension(s) must be declared at login. */
|
||||
public static class UndeclaredServiceExtensionException extends CommandUseErrorException {
|
||||
public UndeclaredServiceExtensionException(Set<String> undeclaredUris) {
|
||||
super(String.format("Service extension(s) must be declared at login: %s",
|
||||
Joiner.on(", ").join(undeclaredUris)));
|
||||
}
|
||||
}
|
||||
}
|
43
java/google/registry/flows/OwnedResourceMutateFlow.java
Normal file
43
java/google/registry/flows/OwnedResourceMutateFlow.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
|
||||
/**
|
||||
* An EPP flow that mutates a single stored resource that is owned by the current registrar.
|
||||
*
|
||||
* @param <R> the resource type being changed
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class OwnedResourceMutateFlow
|
||||
<R extends EppResource, C extends SingleResourceCommand>
|
||||
extends ResourceMutateFlow<R, C> {
|
||||
/** Fail if the object doesn't exist or was deleted. */
|
||||
@Override
|
||||
protected final void verifyMutationAllowed() throws EppException {
|
||||
if (!superuser) {
|
||||
verifyResourceOwnership(getClientId(), existingResource);
|
||||
}
|
||||
verifyMutationOnOwnedResourceAllowed();
|
||||
}
|
||||
|
||||
/** Check invariants before allowing the command to proceed. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyMutationOnOwnedResourceAllowed() throws EppException {}
|
||||
}
|
|
@ -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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.EppResource.Builder;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
|
||||
/**
|
||||
* An EPP flow that acts on an owned resource with a pending transfer on it.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <B> a builder for the resource
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class OwnedResourceMutatePendingTransferFlow
|
||||
<R extends EppResource, B extends Builder<R, ?>, C extends SingleResourceCommand>
|
||||
extends ResourceMutatePendingTransferFlow<R, B, C> {
|
||||
|
||||
/** Fail if this command isn't coming from the registrar that currently owns the resource. */
|
||||
@Override
|
||||
protected final void verifyPendingTransferMutationAllowed() throws EppException {
|
||||
verifyResourceOwnership(getClientId(), existingResource);
|
||||
verifyOwnedResourcePendingTransferMutationAllowed();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyOwnedResourcePendingTransferMutationAllowed() throws EppException {}
|
||||
}
|
85
java/google/registry/flows/ResourceAsyncDeleteFlow.java
Normal file
85
java/google/registry/flows/ResourceAsyncDeleteFlow.java
Normal file
|
@ -0,0 +1,85 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.SuccessWithActionPending;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.domain.registry.flows.EppException.AssociationProhibitsOperationException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.EppResource.Builder;
|
||||
import com.google.domain.registry.model.domain.ReferenceUnion;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.eppoutput.Result.Code;
|
||||
import com.google.domain.registry.model.index.ForeignKeyIndex;
|
||||
|
||||
import com.googlecode.objectify.Work;
|
||||
|
||||
/**
|
||||
* An EPP flow that deletes a resource asynchronously (i.e. via mapreduce).
|
||||
*
|
||||
* @param <R> the resource type being changed
|
||||
* @param <B> a builder for the resource
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceAsyncDeleteFlow
|
||||
<R extends EppResource, B extends Builder<R, ?>, C extends SingleResourceCommand>
|
||||
extends ResourceDeleteFlow<R, C> {
|
||||
|
||||
@Override
|
||||
public void failfast() throws ResourceToDeleteIsReferencedException {
|
||||
// Enter a transactionless context briefly.
|
||||
boolean isLinked = ofy().doTransactionless(new Work<Boolean>() {
|
||||
@Override
|
||||
public Boolean run() {
|
||||
ForeignKeyIndex<R> fki = ForeignKeyIndex.load(resourceClass, targetId, now);
|
||||
if (fki == null) {
|
||||
// Don't failfast on non-existence. We could, but that would duplicate code paths in a way
|
||||
// that would be hard to reason about, and there's no real gain in doing so.
|
||||
return false;
|
||||
}
|
||||
return isLinkedForFailfast(ReferenceUnion.create(fki.getReference()));
|
||||
}
|
||||
});
|
||||
if (isLinked) {
|
||||
throw new ResourceToDeleteIsReferencedException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Subclasses must override this to check if the supplied reference has incoming links. */
|
||||
protected abstract boolean isLinkedForFailfast(ReferenceUnion<R> ref);
|
||||
|
||||
@Override
|
||||
protected final R createOrMutateResource() {
|
||||
@SuppressWarnings("unchecked")
|
||||
B builder = (B) existingResource.asBuilder().addStatusValue(StatusValue.PENDING_DELETE);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Subclasses can override this to return a different success result code. */
|
||||
@Override
|
||||
protected Code getDeleteResultCode() {
|
||||
return SuccessWithActionPending;
|
||||
}
|
||||
|
||||
/** Resource to be deleted has active incoming references. */
|
||||
public static class ResourceToDeleteIsReferencedException
|
||||
extends AssociationProhibitsOperationException {
|
||||
public ResourceToDeleteIsReferencedException() {
|
||||
super("Resource to be deleted has active incoming references");
|
||||
}
|
||||
}
|
||||
}
|
83
java/google/registry/flows/ResourceCheckFlow.java
Normal file
83
java/google/registry/flows/ResourceCheckFlow.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.ResourceCheck;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An EPP flow that checks whether resources can be provisioned.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <C> the overall command type doing the manipulation.
|
||||
*/
|
||||
public abstract class ResourceCheckFlow<R extends EppResource, C extends ResourceCheck>
|
||||
extends ResourceFlow<R, C> {
|
||||
|
||||
protected List<String> targetIds;
|
||||
|
||||
@Override
|
||||
protected final void initResourceFlow() throws EppException {
|
||||
this.targetIds = command.getTargetIds();
|
||||
initCheckResourceFlow();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final EppOutput runResourceFlow() throws EppException {
|
||||
return createOutput(
|
||||
Success,
|
||||
getCheckData(),
|
||||
getResponseExtensions());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyIsAllowed() throws EppException {
|
||||
if (targetIds.size() > RegistryEnvironment.get().config().getMaxChecks()) {
|
||||
throw new TooManyResourceChecksException();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void initCheckResourceFlow() throws EppException {}
|
||||
|
||||
/** Subclasses must implement this to return the check data. */
|
||||
protected abstract CheckData getCheckData();
|
||||
|
||||
/** Subclasses may override this to return extensions. */
|
||||
@SuppressWarnings("unused")
|
||||
protected ImmutableList<? extends ResponseExtension> getResponseExtensions() throws EppException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Too many resource checks requested in one check command. */
|
||||
public static class TooManyResourceChecksException extends ParameterValuePolicyErrorException {
|
||||
public TooManyResourceChecksException() {
|
||||
super(String.format(
|
||||
"No more than %s resources may be checked at a time",
|
||||
RegistryEnvironment.get().config().getMaxChecks()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
129
java/google/registry/flows/ResourceCreateFlow.java
Normal file
129
java/google/registry/flows/ResourceCreateFlow.java
Normal file
|
@ -0,0 +1,129 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.domain.registry.flows.EppException.ObjectAlreadyExistsException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.EppResource.Builder;
|
||||
import com.google.domain.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import com.google.domain.registry.model.domain.DomainApplication;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.ResourceCreateOrChange;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.index.DomainApplicationIndex;
|
||||
import com.google.domain.registry.model.index.EppResourceIndex;
|
||||
import com.google.domain.registry.model.index.ForeignKeyIndex;
|
||||
import com.google.domain.registry.util.TypeUtils.TypeInstantiator;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An EPP flow that creates a storable resource.
|
||||
*
|
||||
* @param <R> the resource type being changed
|
||||
* @param <B> a builder for the resource
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceCreateFlow
|
||||
<R extends EppResource,
|
||||
B extends Builder<R, ?>,
|
||||
C extends ResourceCreateOrChange<? super B> & SingleResourceCommand>
|
||||
extends ResourceCreateOrMutateFlow<R, C> {
|
||||
|
||||
@Override
|
||||
protected void initRepoId() {
|
||||
repoId = createFlowRepoId();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected abstract String createFlowRepoId();
|
||||
|
||||
@Override
|
||||
protected final void verifyIsAllowed() throws EppException {
|
||||
if (existingResource != null) {
|
||||
throw new ResourceAlreadyExistsException(targetId);
|
||||
}
|
||||
verifyCreateIsAllowed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final R createOrMutateResource() throws EppException {
|
||||
B builder = new TypeInstantiator<B>(getClass()){}.instantiate();
|
||||
command.applyTo(builder);
|
||||
builder
|
||||
.setCreationClientId(getClientId())
|
||||
.setCurrentSponsorClientId(getClientId())
|
||||
.setRepoId(getResourceKey().getName());
|
||||
setCreateProperties(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a new or updated {@link ForeignKeyIndex} and {@link EppResourceIndex} pointing to what we
|
||||
* created.
|
||||
*/
|
||||
@Override
|
||||
protected final void modifyRelatedResources() {
|
||||
if (newResource instanceof ForeignKeyedEppResource) {
|
||||
ofy().save().entity(ForeignKeyIndex.create(newResource, newResource.getDeletionTime()));
|
||||
} else if (newResource instanceof DomainApplication) {
|
||||
ofy().save().entity(
|
||||
DomainApplicationIndex.createUpdatedInstance((DomainApplication) newResource));
|
||||
}
|
||||
ofy().save().entity(EppResourceIndex.create(Key.create(newResource)));
|
||||
modifyCreateRelatedResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final String getCreatedRepoId() {
|
||||
return newResource.getRepoId();
|
||||
}
|
||||
|
||||
/** Modify any other resources that need to be informed of this create. */
|
||||
protected void modifyCreateRelatedResources() {}
|
||||
|
||||
/** Check resource-specific invariants before allowing the create to proceed. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyCreateIsAllowed() throws EppException {}
|
||||
|
||||
/** Set any resource-specific properties before creating. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void setCreateProperties(B builder) throws EppException {}
|
||||
|
||||
/** Resource with this id already exists. */
|
||||
public static class ResourceAlreadyExistsException extends ObjectAlreadyExistsException {
|
||||
|
||||
/** Whether this was thrown from a "failfast" context. Useful for testing. */
|
||||
final boolean failfast;
|
||||
|
||||
public ResourceAlreadyExistsException(String resourceId, boolean failfast) {
|
||||
super(String.format("Object with given ID (%s) already exists", resourceId));
|
||||
this.failfast = failfast;
|
||||
}
|
||||
|
||||
public ResourceAlreadyExistsException(String resourceId) {
|
||||
this(resourceId, false);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public boolean isFailfast() {
|
||||
return failfast;
|
||||
}
|
||||
}
|
||||
}
|
168
java/google/registry/flows/ResourceCreateOrMutateFlow.java
Normal file
168
java/google/registry/flows/ResourceCreateOrMutateFlow.java
Normal file
|
@ -0,0 +1,168 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.domain.registry.flows.EppException.AuthorizationErrorException;
|
||||
import com.google.domain.registry.flows.SessionMetadata.SessionSource;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.domain.Period;
|
||||
import com.google.domain.registry.model.domain.metadata.MetadataExtension;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.util.TypeUtils.TypeInstantiator;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
|
||||
/**
|
||||
* An EPP flow that creates or mutates a single stored resource.
|
||||
*
|
||||
* @param <R> the resource type being changed
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*
|
||||
* @error {@link OnlyToolCanPassMetadataException}
|
||||
*/
|
||||
public abstract class ResourceCreateOrMutateFlow
|
||||
<R extends EppResource, C extends SingleResourceCommand> extends SingleResourceFlow<R, C>
|
||||
implements TransactionalFlow {
|
||||
|
||||
String repoId;
|
||||
protected R newResource;
|
||||
protected HistoryEntry historyEntry;
|
||||
protected MetadataExtension metadataExtension;
|
||||
|
||||
@Override
|
||||
protected final void initSingleResourceFlow() throws EppException {
|
||||
metadataExtension = eppInput.getSingleExtension(MetadataExtension.class);
|
||||
initRepoId();
|
||||
initHistoryEntry();
|
||||
initResourceCreateOrMutateFlow();
|
||||
}
|
||||
|
||||
/** Subclasses can optionally override this for further initialization. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void initResourceCreateOrMutateFlow() throws EppException {}
|
||||
|
||||
/**
|
||||
* Initializes the repoId on the flow. For mutate flows, the repoId is the same as that of the
|
||||
* existing resource. For create flows, a new repoId is allocated for the appropriate class.
|
||||
*/
|
||||
protected abstract void initRepoId();
|
||||
|
||||
/**
|
||||
* Create the history entry associated with this resource create or mutate flow.
|
||||
*/
|
||||
private void initHistoryEntry() {
|
||||
// Don't try to create a historyEntry for mutate flows that are failing because the
|
||||
// existingResource doesn't actually exist.
|
||||
historyEntry = (repoId == null) ? null : new HistoryEntry.Builder()
|
||||
.setType(getHistoryEntryType())
|
||||
.setPeriod(getCommandPeriod())
|
||||
.setClientId(getClientId())
|
||||
.setTrid(trid)
|
||||
.setModificationTime(now)
|
||||
.setXmlBytes(storeXmlInHistoryEntry() ? inputXmlBytes : null)
|
||||
.setBySuperuser(superuser)
|
||||
.setReason(getHistoryEntryReason())
|
||||
.setRequestedByRegistrar(getHistoryEntryRequestedByRegistrar())
|
||||
.setParent(getResourceKey())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a Key pointing to this resource, even if this resource hasn't been initialized or
|
||||
* persisted yet.
|
||||
*/
|
||||
protected Key<EppResource> getResourceKey() {
|
||||
checkState(repoId != null,
|
||||
"RepoId hasn't been initialized yet; getResourceKey() called too early");
|
||||
Class<R> resourceClazz = new TypeInstantiator<R>(getClass()){}.getExactType();
|
||||
return Key.<EppResource>create(null, resourceClazz, repoId);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final EppOutput runResourceFlow() throws EppException {
|
||||
newResource = createOrMutateResource();
|
||||
verifyNewStateIsAllowed();
|
||||
validateMetadataExtension();
|
||||
modifyRelatedResources();
|
||||
enqueueTasks();
|
||||
ofy().save().<Object>entities(newResource, historyEntry);
|
||||
return getOutput();
|
||||
}
|
||||
|
||||
/** Execute the inner core of the command and returned the created or mutated resource. */
|
||||
protected abstract R createOrMutateResource() throws EppException;
|
||||
|
||||
/** Check the new state before writing it. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyNewStateIsAllowed() throws EppException {}
|
||||
|
||||
/** Kick off any tasks that need to happen asynchronously. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void enqueueTasks() throws EppException {}
|
||||
|
||||
/** Modify any other resources that need to be informed of this change. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void modifyRelatedResources() throws EppException {}
|
||||
|
||||
/** Ensure that, if a metadata command exists, it is being passed from a tool-created session. */
|
||||
void validateMetadataExtension() throws EppException {
|
||||
if (!(metadataExtension == null
|
||||
|| sessionMetadata.getSessionSource().equals(SessionSource.TOOL))) {
|
||||
throw new OnlyToolCanPassMetadataException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Subclasses must override this to specify the type set on the history entry. */
|
||||
protected abstract HistoryEntry.Type getHistoryEntryType();
|
||||
|
||||
/** Subclasses may override this if they do not wish to store the XML of a command. */
|
||||
protected boolean storeXmlInHistoryEntry() { return true; }
|
||||
|
||||
/** Retrieve the reason for the history entry. */
|
||||
protected String getHistoryEntryReason() {
|
||||
return metadataExtension != null
|
||||
? metadataExtension.getReason()
|
||||
: null;
|
||||
}
|
||||
|
||||
/** Retrieve the requested by registrar flag for the history entry. */
|
||||
protected Boolean getHistoryEntryRequestedByRegistrar() {
|
||||
return metadataExtension != null
|
||||
? metadataExtension.getRequestedByRegistrar()
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses that have a specified period for their command should override this to so that the
|
||||
* history entry contains the correct data.
|
||||
*/
|
||||
protected Period getCommandPeriod() { return null; }
|
||||
|
||||
/** Get the {@link EppOutput} to return. */
|
||||
protected abstract EppOutput getOutput() throws EppException;
|
||||
|
||||
/** Only a tool can pass a metadata extension. */
|
||||
public static class OnlyToolCanPassMetadataException extends AuthorizationErrorException {
|
||||
public OnlyToolCanPassMetadataException() {
|
||||
super("Metadata extensions can only be passed by tools.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
65
java/google/registry/flows/ResourceDeleteFlow.java
Normal file
65
java/google/registry/flows/ResourceDeleteFlow.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
import com.google.domain.registry.model.eppoutput.Result.Code;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that deletes an {@link EppResource}.
|
||||
*
|
||||
* @param <R> the resource type being changed
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceDeleteFlow<R extends EppResource, C extends SingleResourceCommand>
|
||||
extends OwnedResourceMutateFlow<R, C> {
|
||||
|
||||
private static final Set<StatusValue> DELETE_DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.LINKED,
|
||||
StatusValue.CLIENT_DELETE_PROHIBITED,
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_DELETE_PROHIBITED);
|
||||
|
||||
/** This is intentionally non-final so that subclasses can override the disallowed statuses. */
|
||||
@Override
|
||||
protected Set<StatusValue> getDisallowedStatuses() {
|
||||
return DELETE_DISALLOWED_STATUSES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final EppOutput getOutput() {
|
||||
return createOutput(getDeleteResultCode(), null, getDeleteResponseExtensions());
|
||||
}
|
||||
|
||||
/** Subclasses can override this to return a different success result code. */
|
||||
protected Code getDeleteResultCode() {
|
||||
return Success;
|
||||
}
|
||||
|
||||
/** Subclasses can override this to return response extensions. */
|
||||
protected ImmutableList<? extends ResponseExtension> getDeleteResponseExtensions() {
|
||||
return null;
|
||||
}
|
||||
}
|
103
java/google/registry/flows/ResourceFlow.java
Normal file
103
java/google/registry/flows/ResourceFlow.java
Normal 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.flows;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.domain.registry.flows.EppException.CommandUseErrorException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.ResourceCommandWrapper;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.registry.Registry.TldState;
|
||||
import com.google.domain.registry.util.TypeUtils.TypeInstantiator;
|
||||
|
||||
/**
|
||||
* An EPP flow that addresses a stored resource.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <C> the command type doing the manipulation.
|
||||
*/
|
||||
public abstract class ResourceFlow<R extends EppResource, C extends ResourceCommand>
|
||||
extends LoggedInFlow {
|
||||
|
||||
protected C command;
|
||||
protected Class<R> resourceClass;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final void initLoggedInFlow() throws EppException {
|
||||
this.command = (C) ((ResourceCommandWrapper) eppInput.getCommandWrapper().getCommand())
|
||||
.getResourceCommand();
|
||||
this.resourceClass = new TypeInstantiator<R>(getClass()){}.getExactType();
|
||||
initResourceFlow();
|
||||
}
|
||||
|
||||
/** Resource flows can override this for custom initialization.*/
|
||||
protected abstract void initResourceFlow() throws EppException;
|
||||
|
||||
/**
|
||||
* Loads the target resource and performs authorization and state allowance checks on it before
|
||||
* delegating to {@link #runResourceFlow()}.
|
||||
*
|
||||
* @throws EppException If an error occurred while manipulating the resource.
|
||||
*/
|
||||
@Override
|
||||
public final EppOutput run() throws EppException {
|
||||
verifyIsAllowed();
|
||||
return runResourceFlow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that the current action operating within the scope of a single TLD (i.e. an operation on
|
||||
* a domain) is allowed in the registry phase for the specified TLD that the resource is in.
|
||||
*/
|
||||
protected void checkRegistryStateForTld(String tld) throws BadCommandForRegistryPhaseException {
|
||||
if (!superuser && getDisallowedTldStates().contains(Registry.get(tld).getTldState(now))) {
|
||||
throw new BadCommandForRegistryPhaseException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the TLD states during which this command is disallowed. By default all commands can be run
|
||||
* in any state (except predelegation); Flow subclasses must override this method to disallow any
|
||||
* further states.
|
||||
*/
|
||||
protected ImmutableSet<TldState> getDisallowedTldStates() {
|
||||
return Sets.immutableEnumSet(TldState.PREDELEGATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that the command is allowed on the target resource.
|
||||
*
|
||||
* @throws EppException If the command is not allowed on this resource.
|
||||
*/
|
||||
protected abstract void verifyIsAllowed() throws EppException;
|
||||
|
||||
/**
|
||||
* Run the flow.
|
||||
*
|
||||
* @throws EppException If something fails while manipulating the resource.
|
||||
*/
|
||||
protected abstract EppOutput runResourceFlow() throws EppException;
|
||||
|
||||
/** Command is not allowed in the current registry phase. */
|
||||
public static class BadCommandForRegistryPhaseException extends CommandUseErrorException {
|
||||
public BadCommandForRegistryPhaseException() {
|
||||
super("Command is not allowed in the current registry phase");
|
||||
}
|
||||
}
|
||||
}
|
194
java/google/registry/flows/ResourceFlowUtils.java
Normal file
194
java/google/registry/flows/ResourceFlowUtils.java
Normal file
|
@ -0,0 +1,194 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.domain.registry.model.domain.DomainResource.extendRegistrationWithCap;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.domain.registry.flows.EppException.AuthorizationErrorException;
|
||||
import com.google.domain.registry.flows.EppException.InvalidAuthorizationInformationErrorException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.EppResource.Builder;
|
||||
import com.google.domain.registry.model.EppResource.ForeignKeyedEppResource;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.eppcommon.AuthInfo;
|
||||
import com.google.domain.registry.model.eppcommon.AuthInfo.BadAuthInfoException;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppcommon.Trid;
|
||||
import com.google.domain.registry.model.index.ForeignKeyIndex;
|
||||
import com.google.domain.registry.model.poll.PendingActionNotificationResponse;
|
||||
import com.google.domain.registry.model.poll.PendingActionNotificationResponse.ContactPendingActionNotificationResponse;
|
||||
import com.google.domain.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.model.transfer.TransferData;
|
||||
import com.google.domain.registry.model.transfer.TransferResponse;
|
||||
import com.google.domain.registry.model.transfer.TransferResponse.ContactTransferResponse;
|
||||
import com.google.domain.registry.model.transfer.TransferResponse.DomainTransferResponse;
|
||||
import com.google.domain.registry.model.transfer.TransferStatus;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Static utility functions for resource transfer flows. */
|
||||
public class ResourceFlowUtils {
|
||||
|
||||
/** Statuses for which an exDate should be added to transfer responses. */
|
||||
private static final ImmutableSet<TransferStatus> ADD_EXDATE_STATUSES = Sets.immutableEnumSet(
|
||||
TransferStatus.PENDING, TransferStatus.CLIENT_APPROVED, TransferStatus.SERVER_APPROVED);
|
||||
|
||||
/**
|
||||
* Create a transfer response using the id and type of this resource and the specified
|
||||
* {@link TransferData}.
|
||||
*/
|
||||
public static TransferResponse createTransferResponse(
|
||||
EppResource eppResource, TransferData transferData, DateTime now) {
|
||||
assertIsContactOrDomain(eppResource);
|
||||
TransferResponse.Builder<? extends TransferResponse, ?> builder;
|
||||
if (eppResource instanceof ContactResource) {
|
||||
builder = new ContactTransferResponse.Builder().setContactId(eppResource.getForeignKey());
|
||||
} else {
|
||||
DomainResource domain = (DomainResource) eppResource;
|
||||
builder = new DomainTransferResponse.Builder()
|
||||
.setFullyQualifiedDomainNameName(eppResource.getForeignKey())
|
||||
.setExtendedRegistrationExpirationTime(
|
||||
ADD_EXDATE_STATUSES.contains(transferData.getTransferStatus())
|
||||
? extendRegistrationWithCap(
|
||||
now,
|
||||
domain.getRegistrationExpirationTime(),
|
||||
transferData.getExtendedRegistrationYears())
|
||||
: null);
|
||||
}
|
||||
builder.setGainingClientId(transferData.getGainingClientId())
|
||||
.setLosingClientId(transferData.getLosingClientId())
|
||||
.setPendingTransferExpirationTime(transferData.getPendingTransferExpirationTime())
|
||||
.setTransferRequestTime(transferData.getTransferRequestTime())
|
||||
.setTransferStatus(transferData.getTransferStatus());
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a pending action notification response indicating the resolution of a transfer.
|
||||
* <p>
|
||||
* The returned object will use the id and type of this resource, the trid of the resource's last
|
||||
* transfer request, and the specified status and date.
|
||||
*/
|
||||
public static PendingActionNotificationResponse createPendingTransferNotificationResponse(
|
||||
EppResource eppResource,
|
||||
Trid transferRequestTrid,
|
||||
boolean actionResult,
|
||||
DateTime processedDate) {
|
||||
assertIsContactOrDomain(eppResource);
|
||||
return eppResource instanceof ContactResource
|
||||
? ContactPendingActionNotificationResponse.create(
|
||||
eppResource.getForeignKey(), actionResult, transferRequestTrid, processedDate)
|
||||
: DomainPendingActionNotificationResponse.create(
|
||||
eppResource.getForeignKey(), actionResult, transferRequestTrid, processedDate);
|
||||
}
|
||||
|
||||
private static void assertIsContactOrDomain(EppResource eppResource) {
|
||||
checkState(eppResource instanceof ContactResource || eppResource instanceof DomainResource);
|
||||
}
|
||||
|
||||
/** Check that the given clientId corresponds to the owner of given resource. */
|
||||
public static void verifyResourceOwnership(String myClientId, EppResource resource)
|
||||
throws EppException {
|
||||
if (!myClientId.equals(resource.getCurrentSponsorClientId())) {
|
||||
throw new ResourceNotOwnedException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs common deletion operations on an EPP resource and returns a builder for further
|
||||
* modifications. This is broken out into ResourceFlowUtils in order to expose the functionality
|
||||
* to async flows (i.e. mapreduces).
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <R extends EppResource> Builder<R, ? extends Builder<R, ?>>
|
||||
prepareDeletedResourceAsBuilder(R existingResource, DateTime now) {
|
||||
Builder<R, ? extends Builder<R, ?>> builder =
|
||||
(Builder<R, ? extends Builder<R, ?>>) existingResource.asBuilder()
|
||||
.setDeletionTime(now)
|
||||
.setStatusValues(null)
|
||||
.setTransferData(
|
||||
existingResource.getStatusValues().contains(StatusValue.PENDING_TRANSFER)
|
||||
? existingResource.getTransferData().asBuilder()
|
||||
.setTransferStatus(TransferStatus.SERVER_CANCELLED)
|
||||
.setServerApproveEntities(null)
|
||||
.setServerApproveBillingEvent(null)
|
||||
.setServerApproveAutorenewEvent(null)
|
||||
.setServerApproveAutorenewPollMessage(null)
|
||||
.setPendingTransferExpirationTime(null)
|
||||
.build()
|
||||
: existingResource.getTransferData())
|
||||
.wipeOut();
|
||||
return builder;
|
||||
}
|
||||
|
||||
/** Update the relevant {@link ForeignKeyIndex} to cache the new deletion time. */
|
||||
public static <R extends EppResource> void updateForeignKeyIndexDeletionTime(R resource) {
|
||||
if (resource instanceof ForeignKeyedEppResource) {
|
||||
ofy().save().entity(ForeignKeyIndex.create(resource, resource.getDeletionTime()));
|
||||
}
|
||||
}
|
||||
|
||||
/** If there is a transfer out, delete the server-approve entities and enqueue a poll message. */
|
||||
public static <R extends EppResource> void handlePendingTransferOnDelete(
|
||||
R existingResource, R newResource, DateTime now, HistoryEntry historyEntry) {
|
||||
if (existingResource.getStatusValues().contains(StatusValue.PENDING_TRANSFER)) {
|
||||
TransferData oldTransferData = existingResource.getTransferData();
|
||||
ofy().delete().keys(oldTransferData.getServerApproveEntities());
|
||||
ofy().save().entity(new PollMessage.OneTime.Builder()
|
||||
.setClientId(oldTransferData.getGainingClientId())
|
||||
.setEventTime(now)
|
||||
.setMsg(TransferStatus.SERVER_CANCELLED.getMessage())
|
||||
.setResponseData(ImmutableList.of(
|
||||
createTransferResponse(newResource, newResource.getTransferData(), now),
|
||||
createPendingTransferNotificationResponse(
|
||||
existingResource, oldTransferData.getTransferRequestTrid(), false, now)))
|
||||
.setParent(historyEntry)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
/** The specified resource belongs to another client. */
|
||||
public static class ResourceNotOwnedException extends AuthorizationErrorException {
|
||||
public ResourceNotOwnedException() {
|
||||
super("The specified resource belongs to another client");
|
||||
}
|
||||
}
|
||||
|
||||
/** Check that the given AuthInfo is valid for the given resource. */
|
||||
public static void verifyAuthInfoForResource(AuthInfo authInfo, EppResource resource)
|
||||
throws EppException {
|
||||
try {
|
||||
authInfo.verifyAuthorizedFor(resource);
|
||||
} catch (BadAuthInfoException e) {
|
||||
throw new BadAuthInfoForResourceException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Authorization information for accessing resource is invalid. */
|
||||
public static class BadAuthInfoForResourceException
|
||||
extends InvalidAuthorizationInformationErrorException {
|
||||
public BadAuthInfoForResourceException() {
|
||||
super("Authorization information for accessing resource is invalid");
|
||||
}
|
||||
}
|
||||
}
|
49
java/google/registry/flows/ResourceInfoFlow.java
Normal file
49
java/google/registry/flows/ResourceInfoFlow.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.cloneResourceWithLinkedStatus;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseData;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
|
||||
/**
|
||||
* An EPP flow that reads a storable resource.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceInfoFlow<R extends EppResource, C extends SingleResourceCommand>
|
||||
extends ResourceQueryFlow<R, C> {
|
||||
@Override
|
||||
public EppOutput runResourceFlow() throws EppException {
|
||||
return createOutput(Success, getResourceInfo(), getResponseExtensions());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected ResponseData getResourceInfo() throws EppException {
|
||||
return cloneResourceWithLinkedStatus(existingResource, now);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected ImmutableList<? extends ResponseExtension> getResponseExtensions() throws EppException {
|
||||
return null;
|
||||
}
|
||||
}
|
64
java/google/registry/flows/ResourceMutateFlow.java
Normal file
64
java/google/registry/flows/ResourceMutateFlow.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource;
|
||||
|
||||
import com.google.domain.registry.flows.EppException.ObjectDoesNotExistException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.util.TypeUtils.TypeInstantiator;
|
||||
|
||||
/**
|
||||
* An EPP flow that mutates a single stored resource.
|
||||
*
|
||||
* @param <R> the resource type being changed
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceMutateFlow<R extends EppResource, C extends SingleResourceCommand>
|
||||
extends ResourceCreateOrMutateFlow<R, C> {
|
||||
|
||||
@Override
|
||||
protected void initRepoId() {
|
||||
// existingResource could be null here if the flow is being called to mutate a resource that
|
||||
// does not exist, in which case don't throw NPE here and allow the non-existence to be handled
|
||||
// later.
|
||||
repoId = (existingResource == null) ? null : existingResource.getRepoId();
|
||||
}
|
||||
|
||||
/** Fail if the object doesn't exist or was deleted. */
|
||||
@Override
|
||||
protected final void verifyIsAllowed() throws EppException {
|
||||
if (existingResource == null) {
|
||||
throw new ResourceToMutateDoesNotExistException(
|
||||
new TypeInstantiator<R>(getClass()){}.getExactType(), targetId);
|
||||
}
|
||||
if (command.getAuthInfo() != null) {
|
||||
verifyAuthInfoForResource(command.getAuthInfo(), existingResource);
|
||||
}
|
||||
verifyMutationAllowed();
|
||||
}
|
||||
|
||||
/** Check invariants before allowing the command to proceed. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyMutationAllowed() throws EppException {}
|
||||
|
||||
/** Resource with this id does not exist. */
|
||||
public static class ResourceToMutateDoesNotExistException extends ObjectDoesNotExistException {
|
||||
public ResourceToMutateDoesNotExistException(Class<?> type, String targetId) {
|
||||
super(type, targetId);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.createTransferResponse;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.domain.registry.flows.EppException.ObjectNotPendingTransferException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.EppResource.Builder;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.transfer.TransferData;
|
||||
import com.google.domain.registry.model.transfer.TransferStatus;
|
||||
|
||||
/**
|
||||
* An EPP flow that acts on a resource with a pending transfer on it.
|
||||
*
|
||||
* @param <R> the resource type being changed
|
||||
* @param <B> a builder for the resource
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceMutatePendingTransferFlow
|
||||
<R extends EppResource, B extends Builder<R, ?>, C extends SingleResourceCommand>
|
||||
extends ResourceTransferFlow<R, C> {
|
||||
|
||||
/** Fail if object doesn't have a pending transfer, or if authinfo doesn't match. */
|
||||
@Override
|
||||
protected final void verifyMutationAllowed() throws EppException {
|
||||
if (existingResource.getTransferData().getTransferStatus() != TransferStatus.PENDING) {
|
||||
throw new NotPendingTransferException(targetId);
|
||||
}
|
||||
verifyPendingTransferMutationAllowed();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyPendingTransferMutationAllowed() throws EppException {}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final R createOrMutateResource() {
|
||||
TransferData transferData = existingResource.getTransferData();
|
||||
B builder = (B) existingResource.asBuilder()
|
||||
.removeStatusValue(StatusValue.PENDING_TRANSFER)
|
||||
.setTransferData(transferData.asBuilder()
|
||||
.setTransferStatus(getTransferStatus())
|
||||
.setPendingTransferExpirationTime(now)
|
||||
.setExtendedRegistrationYears(null)
|
||||
.setServerApproveEntities(null)
|
||||
.setServerApproveBillingEvent(null)
|
||||
.setServerApproveAutorenewEvent(null)
|
||||
.setServerApproveAutorenewPollMessage(null)
|
||||
.build());
|
||||
setTransferMutateProperties(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Get the new transfer status to set on the resource (and subordinates) after the flow. */
|
||||
protected abstract TransferStatus getTransferStatus();
|
||||
|
||||
/** Set any resource-specific properties for the pending-transfer mutation. */
|
||||
protected void setTransferMutateProperties(@SuppressWarnings("unused") B builder) {}
|
||||
|
||||
/**
|
||||
* Delete the billing event and poll messages that were written in case the transfer would have
|
||||
* been implicitly server approved.
|
||||
*/
|
||||
@Override
|
||||
protected final void modifyRelatedResources() throws EppException {
|
||||
modifyRelatedResourcesForMutateTransfer();
|
||||
ofy().delete().keys(existingResource.getTransferData().getServerApproveEntities());
|
||||
}
|
||||
|
||||
/** Subclasses can override this to make any other model changes that are implied by this flow. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void modifyRelatedResourcesForMutateTransfer() throws EppException {}
|
||||
|
||||
@Override
|
||||
protected final EppOutput getOutput() throws EppException {
|
||||
return createOutput(
|
||||
Success, createTransferResponse(newResource, newResource.getTransferData(), now));
|
||||
}
|
||||
|
||||
/** The resource does not have a pending transfer. */
|
||||
public static class NotPendingTransferException extends ObjectNotPendingTransferException {
|
||||
public NotPendingTransferException(String objectId) {
|
||||
super(objectId);
|
||||
}
|
||||
}
|
||||
}
|
55
java/google/registry/flows/ResourceQueryFlow.java
Normal file
55
java/google/registry/flows/ResourceQueryFlow.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource;
|
||||
|
||||
import com.google.domain.registry.flows.EppException.ObjectDoesNotExistException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.util.TypeUtils.TypeInstantiator;
|
||||
|
||||
/**
|
||||
* An EPP flow that queries a storable resource.
|
||||
*
|
||||
* @param <R> the resource type being queried
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceQueryFlow<R extends EppResource, C extends SingleResourceCommand>
|
||||
extends SingleResourceFlow<R, C> {
|
||||
/** Fail if the object doesn't exist or was deleted. */
|
||||
@Override
|
||||
protected final void verifyIsAllowed() throws EppException {
|
||||
if (existingResource == null) {
|
||||
throw new ResourceToQueryDoesNotExistException(
|
||||
new TypeInstantiator<R>(getClass()){}.getExactType(), targetId);
|
||||
}
|
||||
if (command.getAuthInfo() != null) {
|
||||
verifyAuthInfoForResource(command.getAuthInfo(), existingResource);
|
||||
}
|
||||
verifyQueryIsAllowed();
|
||||
}
|
||||
|
||||
/** Check command- and resource-specific invariants before allowing the query to proceed. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyQueryIsAllowed() throws EppException {}
|
||||
|
||||
/** Resource with this id does not exist. */
|
||||
public static class ResourceToQueryDoesNotExistException extends ObjectDoesNotExistException {
|
||||
public ResourceToQueryDoesNotExistException(Class<?> type, String targetId) {
|
||||
super(type, targetId);
|
||||
}
|
||||
}
|
||||
}
|
59
java/google/registry/flows/ResourceSyncDeleteFlow.java
Normal file
59
java/google/registry/flows/ResourceSyncDeleteFlow.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.handlePendingTransferOnDelete;
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.prepareDeletedResourceAsBuilder;
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.updateForeignKeyIndexDeletionTime;
|
||||
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.EppResource.Builder;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.index.ForeignKeyIndex;
|
||||
|
||||
/**
|
||||
* An EPP flow that deletes a resource synchronously.
|
||||
*
|
||||
* @param <R> the resource type being changed
|
||||
* @param <B> a builder for the resource
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceSyncDeleteFlow
|
||||
<R extends EppResource, B extends Builder<R, ?>, C extends SingleResourceCommand>
|
||||
extends ResourceDeleteFlow<R, C> {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final R createOrMutateResource() {
|
||||
B builder = (B) prepareDeletedResourceAsBuilder(existingResource, now);
|
||||
setDeleteProperties(builder);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Update the relevant {@link ForeignKeyIndex} to cache the new deletion time. */
|
||||
@Override
|
||||
protected final void modifyRelatedResources() throws EppException {
|
||||
updateForeignKeyIndexDeletionTime(newResource);
|
||||
handlePendingTransferOnDelete(existingResource, newResource, now, historyEntry);
|
||||
modifySyncDeleteRelatedResources();
|
||||
}
|
||||
|
||||
/** Set any resource-specific properties before deleting. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void setDeleteProperties(B builder) {}
|
||||
|
||||
/** Modify any other resources that need to be informed of this delete. */
|
||||
protected void modifySyncDeleteRelatedResources() {}
|
||||
}
|
73
java/google/registry/flows/ResourceTransferApproveFlow.java
Normal file
73
java/google/registry/flows/ResourceTransferApproveFlow.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.createPendingTransferNotificationResponse;
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.createTransferResponse;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.EppResource.Builder;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.transfer.TransferData;
|
||||
import com.google.domain.registry.model.transfer.TransferStatus;
|
||||
|
||||
/**
|
||||
* An EPP flow that approves a transfer on a resource.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <B> a builder for the resource
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceTransferApproveFlow
|
||||
<R extends EppResource, B extends Builder<R, ?>, C extends SingleResourceCommand>
|
||||
extends OwnedResourceMutatePendingTransferFlow<R, B, C> {
|
||||
|
||||
@Override
|
||||
protected final TransferStatus getTransferStatus() {
|
||||
return TransferStatus.CLIENT_APPROVED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void setTransferMutateProperties(B builder) {
|
||||
builder.setLastTransferTime(now)
|
||||
.setCurrentSponsorClientId(existingResource.getTransferData().getGainingClientId());
|
||||
setTransferApproveProperties(builder);
|
||||
}
|
||||
|
||||
protected void setTransferApproveProperties(@SuppressWarnings("unused") B builder) {}
|
||||
|
||||
@Override
|
||||
protected void modifyRelatedResourcesForMutateTransfer() throws EppException {
|
||||
// Create a poll message for the gaining client.
|
||||
TransferData oldTransferData = existingResource.getTransferData();
|
||||
ofy().save().entity(new PollMessage.OneTime.Builder()
|
||||
.setClientId(oldTransferData.getGainingClientId())
|
||||
.setEventTime(now)
|
||||
.setMsg(TransferStatus.CLIENT_APPROVED.getMessage())
|
||||
.setResponseData(ImmutableList.of(
|
||||
createTransferResponse(newResource, newResource.getTransferData(), now),
|
||||
createPendingTransferNotificationResponse(
|
||||
existingResource, oldTransferData.getTransferRequestTrid(), true, now)))
|
||||
.setParent(historyEntry)
|
||||
.build());
|
||||
modifyRelatedResourcesForTransferApprove();
|
||||
}
|
||||
|
||||
/** Subclasses can override this to modify other transfer-related resources. */
|
||||
protected void modifyRelatedResourcesForTransferApprove() {}
|
||||
}
|
79
java/google/registry/flows/ResourceTransferCancelFlow.java
Normal file
79
java/google/registry/flows/ResourceTransferCancelFlow.java
Normal file
|
@ -0,0 +1,79 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.createTransferResponse;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.flows.EppException.AuthorizationErrorException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.EppResource.Builder;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.transfer.TransferStatus;
|
||||
|
||||
/**
|
||||
* An EPP flow that cancels a transfer on a resource.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <B> a builder for the resource
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceTransferCancelFlow
|
||||
<R extends EppResource, B extends Builder<R, ?>, C extends SingleResourceCommand>
|
||||
extends ResourceMutatePendingTransferFlow<R, B, C> {
|
||||
|
||||
/** Verify that this is the correct client to cancel this pending transfer. */
|
||||
@Override
|
||||
protected final void verifyPendingTransferMutationAllowed() throws EppException {
|
||||
// TODO(b/18997997): Determine if authInfo is necessary to cancel a transfer.
|
||||
if (!getClientId().equals(existingResource.getTransferData().getGainingClientId())) {
|
||||
throw new NotTransferInitiatorException();
|
||||
}
|
||||
verifyTransferCancelMutationAllowed();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyTransferCancelMutationAllowed() throws EppException {}
|
||||
|
||||
@Override
|
||||
protected void modifyRelatedResourcesForMutateTransfer() throws EppException {
|
||||
ofy().save().entity(new PollMessage.OneTime.Builder()
|
||||
.setClientId(existingResource.getTransferData().getLosingClientId())
|
||||
.setEventTime(now)
|
||||
.setMsg(TransferStatus.CLIENT_CANCELLED.getMessage())
|
||||
.setResponseData(ImmutableList.of(
|
||||
createTransferResponse(newResource, newResource.getTransferData(), now)))
|
||||
.setParent(historyEntry)
|
||||
.build());
|
||||
modifyRelatedResourcesForTransferCancel();
|
||||
}
|
||||
|
||||
/** Subclasses can override this to modify other cancellation-related resources. */
|
||||
protected void modifyRelatedResourcesForTransferCancel() {}
|
||||
|
||||
@Override
|
||||
protected final TransferStatus getTransferStatus() {
|
||||
return TransferStatus.CLIENT_CANCELLED;
|
||||
}
|
||||
|
||||
/** Registrar is not the initiator of this transfer. */
|
||||
public static class NotTransferInitiatorException extends AuthorizationErrorException {
|
||||
public NotTransferInitiatorException() {
|
||||
super("Registrar is not the initiator of this transfer");
|
||||
}
|
||||
}
|
||||
}
|
27
java/google/registry/flows/ResourceTransferFlow.java
Normal file
27
java/google/registry/flows/ResourceTransferFlow.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
// 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.flows;
|
||||
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
|
||||
/**
|
||||
* An EPP flow that involves a transfer on a resource.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceTransferFlow
|
||||
<R extends EppResource, C extends SingleResourceCommand> extends ResourceMutateFlow<R, C> {}
|
72
java/google/registry/flows/ResourceTransferQueryFlow.java
Normal file
72
java/google/registry/flows/ResourceTransferQueryFlow.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.createTransferResponse;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
|
||||
import com.google.domain.registry.flows.EppException.AuthorizationErrorException;
|
||||
import com.google.domain.registry.flows.EppException.CommandUseErrorException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
|
||||
/**
|
||||
* An EPP flow that queries the state of a pending transfer on a resource.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceTransferQueryFlow<R extends EppResource,
|
||||
C extends SingleResourceCommand> extends ResourceQueryFlow<R, C> {
|
||||
|
||||
@Override
|
||||
protected final void verifyQueryIsAllowed() throws EppException {
|
||||
// Most of the fields on the transfer response are required, so there's no way to return valid
|
||||
// XML if the object has never been transferred (and hence the fields aren't populated).
|
||||
if (existingResource.getTransferData().getTransferStatus() == null) {
|
||||
throw new NoTransferHistoryToQueryException();
|
||||
}
|
||||
|
||||
// Note that the authorization info on the command (if present) has already been verified by the
|
||||
// parent class. If it's present, then the other checks are unnecessary.
|
||||
if (command.getAuthInfo() == null &&
|
||||
!getClientId().equals(existingResource.getTransferData().getGainingClientId()) &&
|
||||
!getClientId().equals(existingResource.getTransferData().getLosingClientId())) {
|
||||
throw new NotAuthorizedToViewTransferException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final EppOutput runResourceFlow() throws EppException {
|
||||
return createOutput(
|
||||
Success, createTransferResponse(existingResource, existingResource.getTransferData(), now));
|
||||
}
|
||||
|
||||
/** Registrar is not authorized to view transfer status. */
|
||||
public static class NotAuthorizedToViewTransferException
|
||||
extends AuthorizationErrorException {
|
||||
public NotAuthorizedToViewTransferException() {
|
||||
super("Registrar is not authorized to view transfer status");
|
||||
}
|
||||
}
|
||||
|
||||
/** Object has no transfer history. */
|
||||
public static class NoTransferHistoryToQueryException extends CommandUseErrorException {
|
||||
public NoTransferHistoryToQueryException() {
|
||||
super("Object has no transfer history");
|
||||
}
|
||||
}
|
||||
}
|
63
java/google/registry/flows/ResourceTransferRejectFlow.java
Normal file
63
java/google/registry/flows/ResourceTransferRejectFlow.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.createPendingTransferNotificationResponse;
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.createTransferResponse;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.EppResource.Builder;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.transfer.TransferData;
|
||||
import com.google.domain.registry.model.transfer.TransferStatus;
|
||||
|
||||
/**
|
||||
* An EPP flow that rejects a transfer on a resource.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <B> a builder for the resource
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceTransferRejectFlow
|
||||
<R extends EppResource, B extends Builder<R, ?>, C extends SingleResourceCommand>
|
||||
extends OwnedResourceMutatePendingTransferFlow<R, B, C> {
|
||||
|
||||
@Override
|
||||
protected final TransferStatus getTransferStatus() {
|
||||
return TransferStatus.CLIENT_REJECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void modifyRelatedResourcesForMutateTransfer() throws EppException {
|
||||
TransferData oldTransferData = existingResource.getTransferData();
|
||||
ofy().save().entity(new PollMessage.OneTime.Builder()
|
||||
.setClientId(oldTransferData.getGainingClientId())
|
||||
.setEventTime(now)
|
||||
.setMsg(TransferStatus.CLIENT_REJECTED.getMessage())
|
||||
.setResponseData(ImmutableList.of(
|
||||
createTransferResponse(newResource, newResource.getTransferData(), now),
|
||||
createPendingTransferNotificationResponse(
|
||||
existingResource, oldTransferData.getTransferRequestTrid(), false, now)))
|
||||
.setParent(historyEntry)
|
||||
.build());
|
||||
modifyRelatedResourcesForTransferReject();
|
||||
}
|
||||
|
||||
/** Subclasses can override this to modify other rejection-related resources. */
|
||||
protected void modifyRelatedResourcesForTransferReject() {}
|
||||
}
|
220
java/google/registry/flows/ResourceTransferRequestFlow.java
Normal file
220
java/google/registry/flows/ResourceTransferRequestFlow.java
Normal file
|
@ -0,0 +1,220 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.createPendingTransferNotificationResponse;
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.createTransferResponse;
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.verifyAuthInfoForResource;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.SuccessWithActionPending;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.CollectionUtils.union;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException.AuthorizationErrorException;
|
||||
import com.google.domain.registry.flows.EppException.CommandUseErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ObjectPendingTransferException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseData;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.transfer.TransferData;
|
||||
import com.google.domain.registry.model.transfer.TransferData.TransferServerApproveEntity;
|
||||
import com.google.domain.registry.model.transfer.TransferStatus;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that requests a transfer on a resource.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceTransferRequestFlow
|
||||
<R extends EppResource, C extends SingleResourceCommand> extends ResourceTransferFlow<R, C> {
|
||||
|
||||
private static final Set<StatusValue> TRANSFER_DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.CLIENT_TRANSFER_PROHIBITED,
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_TRANSFER_PROHIBITED);
|
||||
|
||||
private DateTime transferExpirationTime;
|
||||
|
||||
/** Helper class to identify the two clients. */
|
||||
protected abstract class Client {
|
||||
public abstract String getId();
|
||||
}
|
||||
|
||||
/** The gaining client. */
|
||||
protected Client gainingClient = new Client() {
|
||||
@Override
|
||||
public String getId() {
|
||||
return getClientId();
|
||||
}};
|
||||
|
||||
/** The losing client. */
|
||||
protected Client losingClient = new Client() {
|
||||
@Override
|
||||
public String getId() {
|
||||
return existingResource.getCurrentSponsorClientId();
|
||||
}};
|
||||
|
||||
@Override
|
||||
protected final void initResourceCreateOrMutateFlow() {
|
||||
initResourceTransferRequestFlow();
|
||||
}
|
||||
|
||||
protected abstract Duration getAutomaticTransferLength();
|
||||
|
||||
@Override
|
||||
protected final void verifyMutationAllowed() throws EppException {
|
||||
// Verify that the resource does not already have a pending transfer.
|
||||
if (TransferStatus.PENDING.equals(existingResource.getTransferData().getTransferStatus())) {
|
||||
throw new AlreadyPendingTransferException(targetId);
|
||||
}
|
||||
// Verify that this client doesn't already sponsor this resource.
|
||||
if (gainingClient.getId().equals(losingClient.getId())) {
|
||||
throw new ObjectAlreadySponsoredException();
|
||||
}
|
||||
if (command.getAuthInfo() == null) {
|
||||
throw new MissingTransferRequestAuthInfoException();
|
||||
}
|
||||
verifyAuthInfoForResource(command.getAuthInfo(), existingResource);
|
||||
verifyTransferRequestIsAllowed();
|
||||
}
|
||||
|
||||
private TransferData.Builder createTransferDataBuilder(TransferStatus transferStatus) {
|
||||
TransferData.Builder builder = new TransferData.Builder()
|
||||
.setGainingClientId(gainingClient.getId())
|
||||
.setTransferRequestTime(now)
|
||||
.setLosingClientId(losingClient.getId())
|
||||
.setPendingTransferExpirationTime(transferExpirationTime)
|
||||
.setTransferRequestTrid(trid)
|
||||
.setTransferStatus(transferStatus);
|
||||
setTransferDataProperties(builder);
|
||||
return builder;
|
||||
}
|
||||
|
||||
private PollMessage createPollMessage(
|
||||
Client client, TransferStatus transferStatus, DateTime eventTime) {
|
||||
ImmutableList.Builder<ResponseData> responseData = new ImmutableList.Builder<>();
|
||||
responseData.add(createTransferResponse(
|
||||
existingResource, createTransferDataBuilder(transferStatus).build(), now));
|
||||
if (client.getId().equals(gainingClient.getId())) {
|
||||
responseData.add(createPendingTransferNotificationResponse(
|
||||
existingResource, trid, true, now));
|
||||
}
|
||||
return new PollMessage.OneTime.Builder()
|
||||
.setClientId(client.getId())
|
||||
.setEventTime(eventTime)
|
||||
.setMsg(transferStatus.getMessage())
|
||||
.setResponseData(responseData.build())
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final R createOrMutateResource() {
|
||||
// Figure out transfer expiration time once we've verified that the existingResource does in
|
||||
// fact exist (otherwise we won't know which TLD to get this figure off of).
|
||||
transferExpirationTime = now.plus(getAutomaticTransferLength());
|
||||
// When a transfer is requested, a poll message is created to notify the losing registrar.
|
||||
PollMessage requestPollMessage = createPollMessage(losingClient, TransferStatus.PENDING, now);
|
||||
// If the transfer is server approved, this message will be sent to the gaining registrar. */
|
||||
PollMessage serverApproveGainingPollMessage =
|
||||
createPollMessage(gainingClient, TransferStatus.SERVER_APPROVED, transferExpirationTime);
|
||||
// If the transfer is server approved, this message will be sent to the losing registrar. */
|
||||
PollMessage serverApproveLosingPollMessage =
|
||||
createPollMessage(losingClient, TransferStatus.SERVER_APPROVED, transferExpirationTime);
|
||||
ofy().save().entities(
|
||||
requestPollMessage, serverApproveGainingPollMessage, serverApproveLosingPollMessage);
|
||||
return (R) existingResource.asBuilder()
|
||||
.setTransferData(createTransferDataBuilder(TransferStatus.PENDING)
|
||||
.setServerApproveEntities(union(
|
||||
getTransferServerApproveEntities(),
|
||||
Key.create(serverApproveGainingPollMessage),
|
||||
Key.create(serverApproveLosingPollMessage)))
|
||||
.build())
|
||||
.addStatusValue(StatusValue.PENDING_TRANSFER)
|
||||
.build();
|
||||
}
|
||||
|
||||
/** Subclasses can override this to do further initialization. */
|
||||
protected void initResourceTransferRequestFlow() {}
|
||||
|
||||
/**
|
||||
* Subclasses can override this to return the keys of any entities that need to be deleted if the
|
||||
* transfer ends in any state other than SERVER_APPROVED.
|
||||
*/
|
||||
protected Set<Key<? extends TransferServerApproveEntity>> getTransferServerApproveEntities() {
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
|
||||
/** Check resource-specific invariants before allowing the transfer request to proceed. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyTransferRequestIsAllowed() throws EppException {}
|
||||
|
||||
/** Subclasses can override this to modify fields on the transfer data builder. */
|
||||
protected void setTransferDataProperties(
|
||||
@SuppressWarnings("unused") TransferData.Builder builder) {}
|
||||
|
||||
@Override
|
||||
protected final EppOutput getOutput() throws EppException {
|
||||
return createOutput(
|
||||
SuccessWithActionPending,
|
||||
createTransferResponse(newResource, newResource.getTransferData(), now),
|
||||
getTransferResponseExtensions());
|
||||
}
|
||||
|
||||
/** Subclasses can override this to return response extensions. */
|
||||
protected ImmutableList<? extends ResponseExtension> getTransferResponseExtensions() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Set<StatusValue> getDisallowedStatuses() {
|
||||
return TRANSFER_DISALLOWED_STATUSES;
|
||||
}
|
||||
|
||||
/** Authorization info is required to request a transfer. */
|
||||
public static class MissingTransferRequestAuthInfoException extends AuthorizationErrorException {
|
||||
public MissingTransferRequestAuthInfoException() {
|
||||
super("Authorization info is required to request a transfer");
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrar already sponsors the object of this transfer request. */
|
||||
public static class ObjectAlreadySponsoredException extends CommandUseErrorException {
|
||||
public ObjectAlreadySponsoredException() {
|
||||
super("Registrar already sponsors the object of this transfer request");
|
||||
}
|
||||
}
|
||||
|
||||
/** The resource is already pending transfer. */
|
||||
public static class AlreadyPendingTransferException extends ObjectPendingTransferException {
|
||||
public AlreadyPendingTransferException(String targetId) {
|
||||
super(targetId);
|
||||
}
|
||||
}
|
||||
}
|
133
java/google/registry/flows/ResourceUpdateFlow.java
Normal file
133
java/google/registry/flows/ResourceUpdateFlow.java
Normal file
|
@ -0,0 +1,133 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||
import com.google.domain.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.EppResource.Builder;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.AddRemoveSameValueException;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.ResourceUpdate;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that mutates a single stored resource.
|
||||
*
|
||||
* @param <R> the resource type being changed
|
||||
* @param <B> a builder for the resource
|
||||
* @param <C> the command type, marshalled directly from the epp xml
|
||||
*/
|
||||
public abstract class ResourceUpdateFlow
|
||||
<R extends EppResource, B extends Builder<R, ?>, C extends ResourceUpdate<?, ? super B, ?>>
|
||||
extends OwnedResourceMutateFlow<R, C> {
|
||||
|
||||
/**
|
||||
* Note that CLIENT_UPDATE_PROHIBITED is intentionally not in this list. This is because it
|
||||
* requires special checking, since you must be able to clear the status off the object with an
|
||||
* update.
|
||||
*/
|
||||
private static final Set<StatusValue> UPDATE_DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_UPDATE_PROHIBITED);
|
||||
|
||||
@Override
|
||||
protected Set<StatusValue> getDisallowedStatuses() {
|
||||
return UPDATE_DISALLOWED_STATUSES;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyMutationOnOwnedResourceAllowed() throws EppException {
|
||||
for (StatusValue statusValue : Sets.union(
|
||||
command.getInnerAdd().getStatusValues(),
|
||||
command.getInnerRemove().getStatusValues())) {
|
||||
if (!superuser && !statusValue.isClientSettable()) { // The superuser can set any status.
|
||||
throw new StatusNotClientSettableException(statusValue.getXmlName());
|
||||
}
|
||||
}
|
||||
verifyUpdateIsAllowed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final R createOrMutateResource() throws EppException {
|
||||
@SuppressWarnings("unchecked")
|
||||
B builder = (B) existingResource.asBuilder();
|
||||
try {
|
||||
command.applyTo(builder);
|
||||
} catch (AddRemoveSameValueException e) {
|
||||
throw new AddRemoveSameValueEppException();
|
||||
}
|
||||
builder.setLastEppUpdateTime(now).setLastEppUpdateClientId(getClientId());
|
||||
return setUpdateProperties(builder).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyNewStateIsAllowed() throws EppException {
|
||||
// If the resource is marked with clientUpdateProhibited, and this update did not clear that
|
||||
// status, then the update must be disallowed (unless a superuser is requesting the change).
|
||||
if (!superuser
|
||||
&& existingResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)
|
||||
&& newResource.getStatusValues().contains(StatusValue.CLIENT_UPDATE_PROHIBITED)) {
|
||||
throw new ResourceHasClientUpdateProhibitedException();
|
||||
}
|
||||
verifyNewUpdatedStateIsAllowed();
|
||||
}
|
||||
|
||||
/** Subclasses may override this to do more specific checks on the new state after the update. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyNewUpdatedStateIsAllowed() throws EppException {}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyUpdateIsAllowed() throws EppException {}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected B setUpdateProperties(B builder) throws EppException {
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final EppOutput getOutput() {
|
||||
return createOutput(Success);
|
||||
}
|
||||
|
||||
/** The specified status value cannot be set by clients. */
|
||||
public static class StatusNotClientSettableException extends ParameterValueRangeErrorException {
|
||||
public StatusNotClientSettableException(String statusValue) {
|
||||
super(String.format("Status value %s cannot be set by clients", statusValue));
|
||||
}
|
||||
}
|
||||
|
||||
/** This resource has clientUpdateProhibited on it, and the update does not clear that status. */
|
||||
public static class ResourceHasClientUpdateProhibitedException
|
||||
extends StatusProhibitsOperationException {
|
||||
public ResourceHasClientUpdateProhibitedException() {
|
||||
super("Operation disallowed by status: clientUpdateProhibited");
|
||||
}
|
||||
}
|
||||
|
||||
/** Cannot add and remove the same value. */
|
||||
public static class AddRemoveSameValueEppException extends ParameterValuePolicyErrorException {
|
||||
public AddRemoveSameValueEppException() {
|
||||
super("Cannot add and remove the same value");
|
||||
}
|
||||
}
|
||||
}
|
162
java/google/registry/flows/SessionMetadata.java
Normal file
162
java/google/registry/flows/SessionMetadata.java
Normal file
|
@ -0,0 +1,162 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Optional;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/** Class to allow setting and retrieving session information in flows. */
|
||||
public abstract class SessionMetadata {
|
||||
|
||||
/**
|
||||
* An enum that identifies the origin of the session.
|
||||
*/
|
||||
public enum SessionSource {
|
||||
/** e.g. {@code EppConsoleServlet} */
|
||||
CONSOLE,
|
||||
|
||||
/** e.g. {@code EppTlsServlet} */
|
||||
HTTP,
|
||||
|
||||
/** e.g. {@code EppToolServlet} */
|
||||
TOOL,
|
||||
|
||||
/** e.g. {@code LoadTestAction} */
|
||||
LOADTEST,
|
||||
|
||||
/** Direct flow runs (default for e.g. testing) */
|
||||
NONE
|
||||
}
|
||||
|
||||
private TransportCredentials credentials;
|
||||
|
||||
/** The key used for looking up the current client id on the session object. */
|
||||
protected static final String CLIENT_ID_KEY = "CLIENT_ID";
|
||||
|
||||
/** The key used for looking up the superuser bit on the session object. */
|
||||
protected static final String SUPERUSER_KEY = "SUPERUSER";
|
||||
|
||||
/** The key used for looking up the service extensions on the session object. */
|
||||
protected static final String SERVICE_EXTENSIONS_KEY = "SERVICE_EXTENSIONS";
|
||||
|
||||
/** The key used for looking up the number of failed login attempts. */
|
||||
protected static final String FAILED_LOGIN_ATTEMPTS_KEY = "FAILED_LOGIN_ATTEMPTS";
|
||||
|
||||
protected abstract void setProperty(String key, Object value);
|
||||
|
||||
protected abstract Object getProperty(String key);
|
||||
|
||||
/**
|
||||
* Invalidates the session. A new instance must be created after this for future sessions.
|
||||
* Attempts to invoke methods of this class after this method has been called will throw
|
||||
* {@code IllegalStateException}.
|
||||
*/
|
||||
public abstract void invalidate();
|
||||
|
||||
/** Subclasses can override this to verify that this is a valid session. */
|
||||
protected void checkValid() {}
|
||||
|
||||
protected void setPropertyChecked(String key, Object value) {
|
||||
checkValid();
|
||||
setProperty(key, value);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected <T> T getPropertyChecked(String key) {
|
||||
checkValid();
|
||||
return (T) getProperty(key);
|
||||
}
|
||||
|
||||
public TransportCredentials getTransportCredentials() {
|
||||
checkValid();
|
||||
return credentials;
|
||||
}
|
||||
|
||||
public void setTransportCredentials(TransportCredentials credentials) {
|
||||
checkValid();
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
public String getClientId() {
|
||||
return getPropertyChecked(CLIENT_ID_KEY);
|
||||
}
|
||||
|
||||
public boolean isSuperuser() {
|
||||
return Boolean.TRUE.equals(getPropertyChecked(SUPERUSER_KEY));
|
||||
}
|
||||
|
||||
public Set<String> getServiceExtensionUris() {
|
||||
return getPropertyChecked(SERVICE_EXTENSIONS_KEY);
|
||||
}
|
||||
|
||||
public abstract SessionSource getSessionSource();
|
||||
|
||||
/**
|
||||
* Subclasses can override if they present a need to change the session
|
||||
* source at runtime (e.g. anonymous classes created for testing)
|
||||
*/
|
||||
public void setSessionSource(@SuppressWarnings("unused") SessionSource source) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void setClientId(String clientId) {
|
||||
setPropertyChecked(CLIENT_ID_KEY, clientId);
|
||||
}
|
||||
|
||||
public void setSuperuser(boolean superuser) {
|
||||
setPropertyChecked(SUPERUSER_KEY, superuser);
|
||||
}
|
||||
|
||||
public void setServiceExtensionUris(Set<String> serviceExtensionUris) {
|
||||
setPropertyChecked(SERVICE_EXTENSIONS_KEY, checkNotNull(serviceExtensionUris));
|
||||
}
|
||||
|
||||
public int getFailedLoginAttempts() {
|
||||
return ((Integer) Optional.fromNullable(getPropertyChecked(FAILED_LOGIN_ATTEMPTS_KEY)).or(0));
|
||||
}
|
||||
|
||||
public void incrementFailedLoginAttempts() {
|
||||
setPropertyChecked(FAILED_LOGIN_ATTEMPTS_KEY, getFailedLoginAttempts() + 1);
|
||||
}
|
||||
|
||||
public void resetFailedLoginAttempts() {
|
||||
setPropertyChecked(FAILED_LOGIN_ATTEMPTS_KEY, null);
|
||||
}
|
||||
|
||||
// These three methods are here to allow special permissions if a derived class overrides them.
|
||||
|
||||
public boolean isDryRun() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStringHelper(getClass())
|
||||
.add("system hash code", System.identityHashCode(this))
|
||||
.add("clientId", getClientId())
|
||||
.add("isSuperuser", isSuperuser())
|
||||
.add("failedLoginAttempts", getFailedLoginAttempts())
|
||||
.add("sessionSource", getSessionSource())
|
||||
.add("serviceExtensionUris", Joiner.on('.').join(nullToEmpty(getServiceExtensionUris())))
|
||||
.add("transportCredentials", getTransportCredentials())
|
||||
.toString();
|
||||
}
|
||||
}
|
104
java/google/registry/flows/SingleResourceFlow.java
Normal file
104
java/google/registry/flows/SingleResourceFlow.java
Normal 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.flows;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.domain.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.domain.launch.ApplicationIdTargetExtension;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppinput.ResourceCommand.SingleResourceCommand;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that manipulates a single stored resource.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <C> the command type doing the manipulation.
|
||||
*/
|
||||
public abstract class SingleResourceFlow<R extends EppResource, C extends SingleResourceCommand>
|
||||
extends ResourceFlow<R, C> {
|
||||
|
||||
protected R existingResource;
|
||||
protected String targetId;
|
||||
|
||||
@Override
|
||||
protected final void initResourceFlow() throws EppException {
|
||||
targetId = getTargetId();
|
||||
// In a transactional flow, loading the resource will be expensive because it can't be cached.
|
||||
// Allow flows to optionally fail fast here before loading.
|
||||
failfast();
|
||||
// Loads the target resource if it exists
|
||||
// Some flows such as DomainApplicationInfoFlow have the id marked as optional in the schema.
|
||||
// We require it by policy in the relevant flow, but here we need to make sure not to NPE when
|
||||
// initializing the (obviously nonexistent) existing resource.
|
||||
existingResource = (targetId == null || !tryToLoadExisting())
|
||||
? null
|
||||
: loadByUniqueId(resourceClass, targetId, now);
|
||||
if (existingResource != null) {
|
||||
Set<StatusValue> problems = Sets.intersection(
|
||||
existingResource.getStatusValues(), getDisallowedStatuses());
|
||||
if (!problems.isEmpty()) {
|
||||
throw new ResourceStatusProhibitsOperationException(problems);
|
||||
}
|
||||
}
|
||||
initSingleResourceFlow();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the resource flow should attempt to load an existing resource with the
|
||||
* matching targetId. Defaults to true, but overriding flows can set to false to bypass loading
|
||||
* of existing resources.
|
||||
*/
|
||||
protected boolean tryToLoadExisting() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the target id from {@link SingleResourceCommand}. If there is a launch extension present,
|
||||
* it overrides that target id with its application id, so return that instead. There will never
|
||||
* be more than one launch extension.
|
||||
*/
|
||||
protected final String getTargetId() {
|
||||
ApplicationIdTargetExtension extension =
|
||||
eppInput.getSingleExtension(ApplicationIdTargetExtension.class);
|
||||
return extension == null ? command.getTargetId() : extension.getApplicationId();
|
||||
}
|
||||
|
||||
/** Subclasses can optionally override this to fail before loading {@link #existingResource}. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void failfast() throws EppException {}
|
||||
|
||||
/** Subclasses can optionally override this for further initialization. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void initSingleResourceFlow() throws EppException {}
|
||||
|
||||
protected Set<StatusValue> getDisallowedStatuses() {
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
|
||||
/** Resource status prohibits this operation. */
|
||||
public static class ResourceStatusProhibitsOperationException
|
||||
extends StatusProhibitsOperationException {
|
||||
public ResourceStatusProhibitsOperationException(Set<StatusValue> status) {
|
||||
super("Operation disallowed by status: " + Joiner.on(", ").join(status));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,87 @@
|
|||
// 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.flows;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/** A read-only {@link SessionMetadata} that doesn't support login/logout. */
|
||||
public class StatelessRequestSessionMetadata extends SessionMetadata {
|
||||
|
||||
private final String clientId;
|
||||
private final boolean isSuperuser;
|
||||
private final boolean isDryRun;
|
||||
private final Set<String> serviceExtensionUris;
|
||||
private final SessionSource sessionSource;
|
||||
|
||||
public StatelessRequestSessionMetadata(
|
||||
String clientId,
|
||||
boolean isSuperuser,
|
||||
boolean isDryRun,
|
||||
Set<String> serviceExtensionUris,
|
||||
SessionSource source) {
|
||||
this.clientId = clientId;
|
||||
this.isSuperuser = isSuperuser;
|
||||
this.isDryRun = isDryRun;
|
||||
this.serviceExtensionUris = serviceExtensionUris;
|
||||
this.sessionSource = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getClientId() {
|
||||
return clientId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSuperuser() {
|
||||
return isSuperuser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDryRun() {
|
||||
return isDryRun;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getServiceExtensionUris() {
|
||||
return serviceExtensionUris;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SessionSource getSessionSource() {
|
||||
return sessionSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidate() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTransportCredentials(TransportCredentials credentials) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setProperty(String key, Object value) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getProperty(String key) {
|
||||
// We've overridden the getters of all of the properties that we care about. Return null for
|
||||
// everything else so that toString() continues to work.
|
||||
return null;
|
||||
}
|
||||
}
|
180
java/google/registry/flows/TlsCredentials.java
Normal file
180
java/google/registry/flows/TlsCredentials.java
Normal file
|
@ -0,0 +1,180 @@
|
|||
// 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.flows;
|
||||
|
||||
import static com.google.common.base.MoreObjects.toStringHelper;
|
||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.net.HostAndPort;
|
||||
import com.google.common.net.InetAddresses;
|
||||
import com.google.domain.registry.flows.EppException.AuthenticationErrorException;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.util.CidrAddressBlock;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/**
|
||||
* Container and validation for TLS certificate and ip-whitelisting.
|
||||
*/
|
||||
public final class TlsCredentials implements TransportCredentials {
|
||||
|
||||
/** Registrar certificate does not match stored certificate. */
|
||||
public static class BadRegistrarCertificateException extends AuthenticationErrorException {
|
||||
public BadRegistrarCertificateException() {
|
||||
super("Registrar certificate does not match stored certificate");
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrar certificate not present. */
|
||||
public static class MissingRegistrarCertificateException extends AuthenticationErrorException {
|
||||
public MissingRegistrarCertificateException() {
|
||||
super("Registrar certificate not present");
|
||||
}
|
||||
}
|
||||
|
||||
/** SNI header is required. */
|
||||
public static class NoSniException extends AuthenticationErrorException {
|
||||
public NoSniException() {
|
||||
super("SNI header is required");
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrar IP address is not in stored whitelist. */
|
||||
public static class BadRegistrarIpAddressException extends AuthenticationErrorException {
|
||||
public BadRegistrarIpAddressException() {
|
||||
super("Registrar IP address is not in stored whitelist");
|
||||
}
|
||||
}
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
private final String clientCertificateHash;
|
||||
private final InetAddress clientInetAddr;
|
||||
private final String sni;
|
||||
|
||||
@VisibleForTesting
|
||||
public TlsCredentials(String clientCertificateHash, InetAddress clientInetAddr, String sni) {
|
||||
this.clientCertificateHash = clientCertificateHash;
|
||||
this.clientInetAddr = clientInetAddr;
|
||||
this.sni = sni;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the client TLS certificate and source internet address
|
||||
* from the given HTTP request.
|
||||
*/
|
||||
TlsCredentials(HttpServletRequest req) {
|
||||
this(req.getHeader(EppTlsServlet.SSL_CLIENT_CERTIFICATE_HASH_FIELD),
|
||||
parseInetAddress(req.getHeader(EppTlsServlet.FORWARDED_FOR_FIELD)),
|
||||
req.getHeader(EppTlsServlet.REQUESTED_SERVERNAME_VIA_SNI_FIELD));
|
||||
}
|
||||
|
||||
static InetAddress parseInetAddress(String asciiAddr) {
|
||||
try {
|
||||
return InetAddresses.forString(HostAndPort.fromString(asciiAddr).getHostText());
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performsLoginCheck() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if frontend passed us the requested server name. */
|
||||
boolean hasSni() {
|
||||
return !isNullOrEmpty(sni);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Registrar registrar) throws AuthenticationErrorException {
|
||||
validateIp(registrar);
|
||||
validateCertificate(registrar);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies {@link #clientInetAddr} is in CIDR whitelist associated with {@code registrar}.
|
||||
*
|
||||
* @throws BadRegistrarIpAddressException If IP address is not in the whitelist provided
|
||||
*/
|
||||
private void validateIp(Registrar registrar) throws AuthenticationErrorException {
|
||||
ImmutableList<CidrAddressBlock> ipWhitelist = registrar.getIpAddressWhitelist();
|
||||
if (ipWhitelist.isEmpty()) {
|
||||
logger.infofmt("Skipping IP whitelist check because %s doesn't have an IP whitelist",
|
||||
registrar.getClientIdentifier());
|
||||
return;
|
||||
}
|
||||
for (CidrAddressBlock cidrAddressBlock : ipWhitelist) {
|
||||
if (cidrAddressBlock.contains(clientInetAddr)) {
|
||||
// IP address is in whitelist; return early.
|
||||
return;
|
||||
}
|
||||
}
|
||||
logger.infofmt("%s not in %s's CIDR whitelist: %s",
|
||||
clientInetAddr, registrar.getClientIdentifier(), ipWhitelist);
|
||||
throw new BadRegistrarIpAddressException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies client SSL certificate is permitted to issue commands as {@code registrar}.
|
||||
*
|
||||
* @throws NoSniException if frontend didn't send host or certificate hash headers
|
||||
* @throws MissingRegistrarCertificateException if frontend didn't send certificate hash header
|
||||
* @throws BadRegistrarCertificateException if registrar requires certificate and it didn't match
|
||||
*/
|
||||
private void validateCertificate(Registrar registrar) throws AuthenticationErrorException {
|
||||
if (isNullOrEmpty(registrar.getClientCertificateHash())
|
||||
&& isNullOrEmpty(registrar.getFailoverClientCertificateHash())) {
|
||||
logger.infofmt(
|
||||
"Skipping SSL certificate check because %s doesn't have any certificate hashes on file",
|
||||
registrar.getClientIdentifier());
|
||||
return;
|
||||
}
|
||||
if (isNullOrEmpty(clientCertificateHash)) {
|
||||
// If there's no SNI header that's probably why we don't have a cert, so send a specific
|
||||
// message. Otherwise, send a missing certificate message.
|
||||
if (!hasSni()) {
|
||||
throw new NoSniException();
|
||||
}
|
||||
logger.infofmt("Request did not include %s", EppTlsServlet.SSL_CLIENT_CERTIFICATE_HASH_FIELD);
|
||||
throw new MissingRegistrarCertificateException();
|
||||
}
|
||||
if (!clientCertificateHash.equals(registrar.getClientCertificateHash())
|
||||
&& !clientCertificateHash.equals(registrar.getFailoverClientCertificateHash())) {
|
||||
logger.warningfmt("bad certificate hash (%s) for %s, wanted either %s or %s",
|
||||
clientCertificateHash,
|
||||
registrar.getClientIdentifier(),
|
||||
registrar.getClientCertificateHash(),
|
||||
registrar.getFailoverClientCertificateHash());
|
||||
throw new BadRegistrarCertificateException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toStringHelper(getClass())
|
||||
.add("system hash code", System.identityHashCode(this))
|
||||
.add("clientCertificateHash", clientCertificateHash)
|
||||
.add("clientInetAddress", clientInetAddr)
|
||||
.add("sni", sni)
|
||||
.toString();
|
||||
}
|
||||
}
|
23
java/google/registry/flows/TransactionalFlow.java
Normal file
23
java/google/registry/flows/TransactionalFlow.java
Normal file
|
@ -0,0 +1,23 @@
|
|||
// 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.flows;
|
||||
|
||||
/**
|
||||
* Marker interface indicating that a {@link Flow} needs to be run transactionally.
|
||||
* <p>
|
||||
* Any flow that mutates the datastore should be tagged with this so that {@link FlowRunner} will
|
||||
* know how to run it.
|
||||
*/
|
||||
public interface TransactionalFlow {}
|
39
java/google/registry/flows/TransportCredentials.java
Normal file
39
java/google/registry/flows/TransportCredentials.java
Normal file
|
@ -0,0 +1,39 @@
|
|||
// 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.flows;
|
||||
|
||||
import com.google.domain.registry.flows.EppException.AuthenticationErrorException;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
|
||||
/**
|
||||
* A marker interface for objects containing registrar credentials provided via an EPP transport.
|
||||
*/
|
||||
public interface TransportCredentials {
|
||||
|
||||
/**
|
||||
* Indicates whether the transport takes the place of EPP login checks, in which case LoginFlow
|
||||
* will not check the password. Alternatively, if the password should be checked, it MUST match
|
||||
* the user's and GAE's isUserAdmin should not be used to bypass this check as internal
|
||||
* connections over RPC will have this property for all registrars.
|
||||
*/
|
||||
boolean performsLoginCheck();
|
||||
|
||||
/**
|
||||
* Called by {@link com.google.domain.registry.flows.session.LoginFlow LoginFlow}
|
||||
* to check the transport credentials against the stored registrar's credentials.
|
||||
* If they do not match, throw an AuthenticationErrorException.
|
||||
*/
|
||||
void validate(Registrar r) throws AuthenticationErrorException;
|
||||
}
|
66
java/google/registry/flows/async/AsyncFlowUtils.java
Normal file
66
java/google/registry/flows/async/AsyncFlowUtils.java
Normal file
|
@ -0,0 +1,66 @@
|
|||
// 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.flows.async;
|
||||
|
||||
import static com.google.domain.registry.request.Actions.getPathForAction;
|
||||
|
||||
import com.google.appengine.api.taskqueue.Queue;
|
||||
import com.google.appengine.api.taskqueue.QueueFactory;
|
||||
import com.google.appengine.api.taskqueue.RetryOptions;
|
||||
import com.google.appengine.api.taskqueue.TaskHandle;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions;
|
||||
import com.google.appengine.api.taskqueue.TaskOptions.Method;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.mapreduce.MapreduceAction;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/** Utility methods specific to async flows. */
|
||||
public final class AsyncFlowUtils {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
@VisibleForTesting
|
||||
public static final String ASYNC_FLOW_QUEUE_NAME = "flows-async"; // See queue.xml.
|
||||
|
||||
private AsyncFlowUtils() {}
|
||||
|
||||
/** Enqueues a mapreduce action to perform an async flow operation. */
|
||||
public static TaskHandle enqueueMapreduceAction(
|
||||
Class<? extends MapreduceAction> action,
|
||||
ImmutableMap<String, String> params,
|
||||
Duration executionDelay) {
|
||||
Queue queue = QueueFactory.getQueue(ASYNC_FLOW_QUEUE_NAME);
|
||||
String path = getPathForAction(action);
|
||||
logger.infofmt("Enqueueing async mapreduce action with path %s and params %s", path, params);
|
||||
// Aggressively back off if the task fails, to minimize flooding the logs.
|
||||
RetryOptions retryOptions = RetryOptions.Builder.withMinBackoffSeconds(
|
||||
RegistryEnvironment.get().config().getAsyncFlowFailureBackoff().getStandardSeconds());
|
||||
TaskOptions options = TaskOptions.Builder
|
||||
.withUrl(path)
|
||||
.retryOptions(retryOptions)
|
||||
.countdownMillis(executionDelay.getMillis())
|
||||
.method(Method.GET);
|
||||
for (Entry<String, String> entry : params.entrySet()) {
|
||||
options.param(entry.getKey(), entry.getValue());
|
||||
}
|
||||
return queue.add(options);
|
||||
}
|
||||
}
|
58
java/google/registry/flows/async/AsyncFlowsModule.java
Normal file
58
java/google/registry/flows/async/AsyncFlowsModule.java
Normal file
|
@ -0,0 +1,58 @@
|
|||
// 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.flows.async;
|
||||
|
||||
import static com.google.domain.registry.flows.async.DeleteEppResourceAction.PARAM_IS_SUPERUSER;
|
||||
import static com.google.domain.registry.flows.async.DeleteEppResourceAction.PARAM_REQUESTING_CLIENT_ID;
|
||||
import static com.google.domain.registry.flows.async.DeleteEppResourceAction.PARAM_RESOURCE_KEY;
|
||||
import static com.google.domain.registry.flows.async.DnsRefreshForHostRenameAction.PARAM_HOST_KEY;
|
||||
import static com.google.domain.registry.request.RequestParameters.extractBooleanParameter;
|
||||
import static com.google.domain.registry.request.RequestParameters.extractRequiredParameter;
|
||||
|
||||
import com.google.domain.registry.request.Parameter;
|
||||
|
||||
import dagger.Module;
|
||||
import dagger.Provides;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
/** Dagger module for the async flows package. */
|
||||
@Module
|
||||
public final class AsyncFlowsModule {
|
||||
|
||||
@Provides
|
||||
@Parameter(PARAM_IS_SUPERUSER)
|
||||
static boolean provideIsSuperuser(HttpServletRequest req) {
|
||||
return extractBooleanParameter(req, PARAM_IS_SUPERUSER);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter(PARAM_REQUESTING_CLIENT_ID)
|
||||
static String provideRequestingClientId(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, PARAM_REQUESTING_CLIENT_ID);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter(PARAM_RESOURCE_KEY)
|
||||
static String provideResourceKey(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, PARAM_RESOURCE_KEY);
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Parameter(PARAM_HOST_KEY)
|
||||
static String provideHostKey(HttpServletRequest req) {
|
||||
return extractRequiredParameter(req, PARAM_HOST_KEY);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
// 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.flows.async;
|
||||
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.handlePendingTransferOnDelete;
|
||||
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.domain.DomainBase;
|
||||
import com.google.domain.registry.model.domain.ReferenceUnion;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry.Type;
|
||||
import com.google.domain.registry.request.Action;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* A mapreduce to delete the specified ContactResource, but ONLY if it is not referred to by any
|
||||
* existing DomainBase entity.
|
||||
*/
|
||||
@Action(path = "/_dr/task/deleteContactResource")
|
||||
public class DeleteContactResourceAction extends DeleteEppResourceAction<ContactResource> {
|
||||
|
||||
@Inject
|
||||
public DeleteContactResourceAction() {
|
||||
super(
|
||||
new DeleteContactResourceMapper(),
|
||||
new DeleteContactResourceReducer());
|
||||
}
|
||||
|
||||
/** An async deletion mapper for {@link ContactResource}. */
|
||||
public static class DeleteContactResourceMapper extends DeleteEppResourceMapper<ContactResource> {
|
||||
|
||||
private static final long serialVersionUID = -5904009575877950342L;
|
||||
|
||||
@Override
|
||||
protected boolean isLinked(
|
||||
DomainBase domain, ReferenceUnion<ContactResource> targetResourceRef) {
|
||||
return domain.getReferencedContacts().contains(targetResourceRef);
|
||||
}
|
||||
}
|
||||
|
||||
/** An async deletion reducer for {@link ContactResource}. */
|
||||
public static class DeleteContactResourceReducer
|
||||
extends DeleteEppResourceReducer<ContactResource> {
|
||||
|
||||
private static final long serialVersionUID = -7633644054441045215L;
|
||||
|
||||
@Override
|
||||
protected Type getHistoryType(boolean successfulDelete) {
|
||||
return successfulDelete
|
||||
? HistoryEntry.Type.CONTACT_DELETE
|
||||
: HistoryEntry.Type.CONTACT_DELETE_FAILURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performDeleteTasks(
|
||||
ContactResource targetResource,
|
||||
ContactResource deletedResource,
|
||||
DateTime deletionTime,
|
||||
HistoryEntry historyEntryForDelete) {
|
||||
handlePendingTransferOnDelete(
|
||||
targetResource,
|
||||
deletedResource,
|
||||
deletionTime,
|
||||
historyEntryForDelete);
|
||||
}
|
||||
}
|
||||
}
|
272
java/google/registry/flows/async/DeleteEppResourceAction.java
Normal file
272
java/google/registry/flows/async/DeleteEppResourceAction.java
Normal file
|
@ -0,0 +1,272 @@
|
|||
// 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.flows.async;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.prepareDeletedResourceAsBuilder;
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.updateForeignKeyIndexDeletionTime;
|
||||
import static com.google.domain.registry.model.EppResourceUtils.isActive;
|
||||
import static com.google.domain.registry.model.EppResourceUtils.isDeleted;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.PipelineUtils.createJobPath;
|
||||
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.appengine.tools.mapreduce.Mapper;
|
||||
import com.google.appengine.tools.mapreduce.Reducer;
|
||||
import com.google.appengine.tools.mapreduce.ReducerInput;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterators;
|
||||
import com.google.domain.registry.mapreduce.MapreduceAction;
|
||||
import com.google.domain.registry.mapreduce.MapreduceRunner;
|
||||
import com.google.domain.registry.mapreduce.inputs.EppResourceInputs;
|
||||
import com.google.domain.registry.mapreduce.inputs.NullInput;
|
||||
import com.google.domain.registry.model.EppResource;
|
||||
import com.google.domain.registry.model.annotations.ExternalMessagingName;
|
||||
import com.google.domain.registry.model.domain.DomainBase;
|
||||
import com.google.domain.registry.model.domain.ReferenceUnion;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
import com.google.domain.registry.request.Parameter;
|
||||
import com.google.domain.registry.request.Response;
|
||||
import com.google.domain.registry.util.Clock;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
import com.google.domain.registry.util.NonFinalForTesting;
|
||||
import com.google.domain.registry.util.SystemClock;
|
||||
import com.google.domain.registry.util.TypeUtils.TypeInstantiator;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.Ref;
|
||||
import com.googlecode.objectify.Work;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* A mapreduce to delete the specified EPP resource, but ONLY if it is not referred to by any
|
||||
* existing DomainBase entity.
|
||||
*/
|
||||
public abstract class DeleteEppResourceAction<T extends EppResource> implements MapreduceAction {
|
||||
|
||||
@NonFinalForTesting
|
||||
static Clock clock = new SystemClock();
|
||||
|
||||
/** The HTTP parameter name used to specify the websafe key of the resource to delete. */
|
||||
public static final String PARAM_RESOURCE_KEY = "resourceKey";
|
||||
public static final String PARAM_REQUESTING_CLIENT_ID = "requestingClientId";
|
||||
public static final String PARAM_IS_SUPERUSER = "superuser";
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
@Inject @Parameter(PARAM_RESOURCE_KEY) String resourceKeyString;
|
||||
@Inject @Parameter(PARAM_REQUESTING_CLIENT_ID) String requestingClientId;
|
||||
@Inject @Parameter(PARAM_IS_SUPERUSER) boolean isSuperuser;
|
||||
@Inject MapreduceRunner mrRunner;
|
||||
@Inject Response response;
|
||||
|
||||
DeleteEppResourceMapper<T> mapper;
|
||||
DeleteEppResourceReducer<T> reducer;
|
||||
|
||||
protected DeleteEppResourceAction(
|
||||
DeleteEppResourceMapper<T> mapper,
|
||||
DeleteEppResourceReducer<T> reducer) {
|
||||
this.mapper = mapper;
|
||||
this.reducer = reducer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Key<T> resourceKey = null;
|
||||
T resource;
|
||||
try {
|
||||
resourceKey = Key.create(resourceKeyString);
|
||||
resource = checkArgumentNotNull(ofy().load().key(resourceKey).now());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException(resourceKey == null
|
||||
? "Could not parse key string: " + resourceKeyString
|
||||
: "Could not load resource for key: " + resourceKey);
|
||||
}
|
||||
checkArgument(
|
||||
resource.getClass().equals(new TypeInstantiator<T>(getClass()){}.getExactType()),
|
||||
String.format("Cannot delete a %s via this action.", resource.getClass().getSimpleName()));
|
||||
checkState(
|
||||
!isDeleted(resource, clock.nowUtc()),
|
||||
"Resource %s is already deleted.", resource.getForeignKey());
|
||||
checkState(
|
||||
resource.getStatusValues().contains(StatusValue.PENDING_DELETE),
|
||||
"Resource %s is not set as PENDING_DELETE", resource.getForeignKey());
|
||||
mapper.setTargetResource(resourceKey);
|
||||
reducer.setClient(requestingClientId, isSuperuser);
|
||||
logger.infofmt("Executing Delete EPP resource mapreduce for %s", resourceKey);
|
||||
response.sendJavaScriptRedirect(createJobPath(mrRunner
|
||||
.setJobName("Check for EPP resource references and then delete")
|
||||
.setModuleName("backend")
|
||||
.runMapreduce(
|
||||
mapper,
|
||||
reducer,
|
||||
ImmutableList.of(
|
||||
// Add an extra shard that maps over a null domain. See the mapper code for why.
|
||||
new NullInput<DomainBase>(),
|
||||
EppResourceInputs.createEntityInput(DomainBase.class)))));
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapper that iterates over all {@link DomainBase} entities.
|
||||
*
|
||||
* <p>It emits the target key and {@code true} for domains referencing the target resource. For
|
||||
* the special input of {@code null} it emits the target key and {@code false}.
|
||||
*/
|
||||
public abstract static class DeleteEppResourceMapper<T extends EppResource>
|
||||
extends Mapper<DomainBase, Key<T>, Boolean> {
|
||||
|
||||
private static final long serialVersionUID = -7355145176854995813L;
|
||||
|
||||
private DateTime targetResourceUpdateTimestamp;
|
||||
private Key<T> targetEppResourceKey;
|
||||
|
||||
private void setTargetResource(Key<T> targetEppResourceKey) {
|
||||
this.targetEppResourceKey = targetEppResourceKey;
|
||||
this.targetResourceUpdateTimestamp =
|
||||
ofy().load().key(targetEppResourceKey).now().getUpdateAutoTimestamp().getTimestamp();
|
||||
}
|
||||
|
||||
/** Determine whether the target resource is a linked resource on the domain. */
|
||||
protected abstract boolean isLinked(DomainBase domain, ReferenceUnion<T> targetResourceRef);
|
||||
|
||||
@Override
|
||||
public void map(DomainBase domain) {
|
||||
// The reducer only runs if at least one value is emitted. We add a null input to the
|
||||
// mapreduce and always emit 'false' for it to force the reducer to run. We can then emit
|
||||
// 'true' for linked domains and not emit anything for unlinked domains, which speeds up the
|
||||
// reducer since it will only receive true keys, of which there will be few (usually none).
|
||||
if (domain == null) {
|
||||
emit(targetEppResourceKey, false);
|
||||
return;
|
||||
}
|
||||
// The ReferenceUnion can't be a field on the Mapper, because when a Ref<?> is serialized
|
||||
// (required for each MapShardTask), it uses the DeadRef version, which contains the Ref's
|
||||
// value, which isn't serializable. Thankfully, this isn't expensive.
|
||||
// See: https://github.com/objectify/objectify/blob/master/src/main/java/com/googlecode/objectify/impl/ref/DeadRef.java
|
||||
if (isActive(domain, targetResourceUpdateTimestamp)
|
||||
&& isLinked(domain, ReferenceUnion.create(Ref.create(targetEppResourceKey)))) {
|
||||
emit(targetEppResourceKey, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reducer that checks if the EPP resource to be deleted is referenced anywhere, and then
|
||||
* deletes it if not and unmarks it for deletion if so.
|
||||
*/
|
||||
public abstract static class DeleteEppResourceReducer<T extends EppResource>
|
||||
extends Reducer<Key<T>, Boolean, Void> {
|
||||
|
||||
private static final long serialVersionUID = 875017002097945151L;
|
||||
|
||||
private String requestingClientId;
|
||||
private boolean isSuperuser;
|
||||
|
||||
private void setClient(String requestingClientId, boolean isSuperuser) {
|
||||
this.requestingClientId = requestingClientId;
|
||||
this.isSuperuser = isSuperuser;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the proper history entry type for the delete operation, as a function of
|
||||
* whether or not the delete was successful.
|
||||
*/
|
||||
protected abstract HistoryEntry.Type getHistoryType(boolean successfulDelete);
|
||||
|
||||
/** Perform any type-specific tasks on the resource to be deleted (and/or its dependencies). */
|
||||
protected abstract void performDeleteTasks(
|
||||
T targetResource,
|
||||
T deletedResource,
|
||||
DateTime deletionTime,
|
||||
HistoryEntry historyEntryForDelete);
|
||||
|
||||
@Override
|
||||
public void reduce(final Key<T> key, final ReducerInput<Boolean> values) {
|
||||
final boolean hasNoActiveReferences = !Iterators.contains(values, true);
|
||||
logger.infofmt("Processing delete request for %s", key.toString());
|
||||
String pollMessageText = ofy().transactNew(new Work<String>() {
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public String run() {
|
||||
DateTime now = ofy().getTransactionTime();
|
||||
T targetResource = (T) ofy().load().key(key).now().cloneProjectedAtTime(now);
|
||||
String resourceName = targetResource.getForeignKey();
|
||||
// Double-check that the resource is still active and in PENDING_DELETE within the
|
||||
// transaction.
|
||||
checkState(
|
||||
!isDeleted(targetResource, now),
|
||||
"Resource %s is already deleted.", resourceName);
|
||||
checkState(
|
||||
targetResource.getStatusValues().contains(StatusValue.PENDING_DELETE),
|
||||
"Resource %s is not in PENDING_DELETE.", resourceName);
|
||||
|
||||
targetResource = (T) targetResource.asBuilder()
|
||||
.removeStatusValue(StatusValue.PENDING_DELETE)
|
||||
.build();
|
||||
|
||||
boolean requestedByCurrentOwner =
|
||||
targetResource.getCurrentSponsorClientId().equals(requestingClientId);
|
||||
boolean deleteAllowed = hasNoActiveReferences && (requestedByCurrentOwner || isSuperuser);
|
||||
|
||||
String resourceTypeName =
|
||||
targetResource.getClass().getAnnotation(ExternalMessagingName.class).value();
|
||||
HistoryEntry.Type historyType = getHistoryType(deleteAllowed);
|
||||
|
||||
String pollMessageText = deleteAllowed
|
||||
? String.format("Deleted %s %s.", resourceTypeName, resourceName)
|
||||
: String.format(
|
||||
"Can't delete %s %s because %s.",
|
||||
resourceTypeName,
|
||||
resourceName,
|
||||
requestedByCurrentOwner
|
||||
? "it is referenced by a domain"
|
||||
: "it was transferred prior to deletion");
|
||||
|
||||
HistoryEntry historyEntry = new HistoryEntry.Builder()
|
||||
.setClientId(requestingClientId)
|
||||
.setModificationTime(now)
|
||||
.setType(historyType)
|
||||
.setParent(key)
|
||||
.build();
|
||||
|
||||
PollMessage.OneTime deleteResultMessage = new PollMessage.OneTime.Builder()
|
||||
.setClientId(requestingClientId)
|
||||
.setMsg(pollMessageText)
|
||||
.setParent(historyEntry)
|
||||
.setEventTime(now)
|
||||
.build();
|
||||
|
||||
if (deleteAllowed) {
|
||||
T deletedResource = prepareDeletedResourceAsBuilder(targetResource, now).build();
|
||||
performDeleteTasks(targetResource, deletedResource, now, historyEntry);
|
||||
updateForeignKeyIndexDeletionTime(deletedResource);
|
||||
ofy().save().<Object>entities(deletedResource, historyEntry, deleteResultMessage);
|
||||
} else {
|
||||
ofy().save().<Object>entities(targetResource, historyEntry, deleteResultMessage);
|
||||
}
|
||||
return pollMessageText;
|
||||
}
|
||||
});
|
||||
logger.infofmt(pollMessageText);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
// 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.flows.async;
|
||||
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.domain.registry.dns.DnsQueue;
|
||||
import com.google.domain.registry.model.domain.DomainBase;
|
||||
import com.google.domain.registry.model.domain.ReferenceUnion;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry.Type;
|
||||
import com.google.domain.registry.request.Action;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* A mapreduce to delete the specified HostResource, but ONLY if it is not referred to by any
|
||||
* existing DomainBase entity.
|
||||
*/
|
||||
@Action(path = "/_dr/task/deleteHostResource")
|
||||
public class DeleteHostResourceAction extends DeleteEppResourceAction<HostResource> {
|
||||
|
||||
@Inject
|
||||
public DeleteHostResourceAction() {
|
||||
super(
|
||||
new DeleteHostResourceMapper(),
|
||||
new DeleteHostResourceReducer());
|
||||
}
|
||||
|
||||
/** An async deletion mapper for {@link HostResource}. */
|
||||
public static class DeleteHostResourceMapper extends DeleteEppResourceMapper<HostResource> {
|
||||
|
||||
private static final long serialVersionUID = 1941092742903217194L;
|
||||
|
||||
@Override
|
||||
protected boolean isLinked(
|
||||
DomainBase domain, ReferenceUnion<HostResource> targetResourceRef) {
|
||||
return domain.getNameservers().contains(targetResourceRef);
|
||||
}
|
||||
}
|
||||
|
||||
/** An async deletion reducer for {@link HostResource}. */
|
||||
public static class DeleteHostResourceReducer extends DeleteEppResourceReducer<HostResource> {
|
||||
|
||||
private static final long serialVersionUID = 555457935288867324L;
|
||||
|
||||
@Override
|
||||
protected Type getHistoryType(boolean successfulDelete) {
|
||||
return successfulDelete
|
||||
? HistoryEntry.Type.HOST_DELETE
|
||||
: HistoryEntry.Type.HOST_DELETE_FAILURE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void performDeleteTasks(
|
||||
HostResource targetResource,
|
||||
HostResource deletedResource,
|
||||
DateTime deletionTime,
|
||||
HistoryEntry historyEntryForDelete) {
|
||||
if (targetResource.getSuperordinateDomain() != null) {
|
||||
DnsQueue.create().addHostRefreshTask(targetResource.getFullyQualifiedHostName());
|
||||
ofy().save().entity(
|
||||
targetResource.getSuperordinateDomain().get().asBuilder()
|
||||
.removeSubordinateHost(targetResource.getFullyQualifiedHostName())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
// 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.flows.async;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.isActive;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.PipelineUtils.createJobPath;
|
||||
import static com.google.domain.registry.util.PreconditionsUtils.checkArgumentNotNull;
|
||||
|
||||
import com.google.appengine.tools.mapreduce.Mapper;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.dns.DnsQueue;
|
||||
import com.google.domain.registry.mapreduce.MapreduceAction;
|
||||
import com.google.domain.registry.mapreduce.MapreduceRunner;
|
||||
import com.google.domain.registry.mapreduce.inputs.EppResourceInputs;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.ReferenceUnion;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
import com.google.domain.registry.request.Action;
|
||||
import com.google.domain.registry.request.HttpException.BadRequestException;
|
||||
import com.google.domain.registry.request.Parameter;
|
||||
import com.google.domain.registry.request.Response;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
import com.google.domain.registry.util.NonFinalForTesting;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Enqueues DNS refreshes for applicable domains following a host rename.
|
||||
*/
|
||||
@Action(path = "/_dr/task/dnsRefreshForHostRename")
|
||||
public class DnsRefreshForHostRenameAction implements MapreduceAction {
|
||||
|
||||
/** The HTTP parameter name used to specify the websafe key of the host to rename. */
|
||||
public static final String PARAM_HOST_KEY = "hostKey";
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
@NonFinalForTesting
|
||||
static DnsQueue dnsQueue = DnsQueue.create();
|
||||
|
||||
@Inject @Parameter(PARAM_HOST_KEY) String hostKeyString;
|
||||
@Inject MapreduceRunner mrRunner;
|
||||
@Inject Response response;
|
||||
@Inject DnsRefreshForHostRenameAction() {}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Key<HostResource> resourceKey = null;
|
||||
HostResource host;
|
||||
try {
|
||||
resourceKey = Key.create(hostKeyString);
|
||||
host = checkArgumentNotNull(ofy().load().key(resourceKey).now());
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new BadRequestException(resourceKey == null
|
||||
? "Could not parse key string: " + hostKeyString
|
||||
: "Could not load resource for key: " + resourceKey);
|
||||
}
|
||||
response.sendJavaScriptRedirect(createJobPath(mrRunner
|
||||
.setJobName("Enqueue DNS refreshes for domains following a host rename")
|
||||
.setModuleName("backend")
|
||||
.runMapOnly(
|
||||
new DnsRefreshForHostRenameMapper(host),
|
||||
ImmutableList.of(EppResourceInputs.createEntityInput(DomainResource.class)))));
|
||||
}
|
||||
|
||||
/** Map over domains and refresh the dns of those that referenced this host. */
|
||||
public static class DnsRefreshForHostRenameMapper extends Mapper<DomainResource, Void, Void> {
|
||||
|
||||
private static final long serialVersionUID = -4707015136971008447L;
|
||||
|
||||
private final DateTime hostUpdateTime;
|
||||
private final Key<HostResource> targetHostKey;
|
||||
|
||||
DnsRefreshForHostRenameMapper(HostResource host) {
|
||||
this.targetHostKey = Key.create(host);
|
||||
this.hostUpdateTime = host.getUpdateAutoTimestamp().getTimestamp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void map(DomainResource domain) {
|
||||
if (isActive(domain, hostUpdateTime)
|
||||
&& domain.getNameservers().contains(ReferenceUnion.create(Ref.create(targetHostKey)))) {
|
||||
try {
|
||||
dnsQueue.addDomainRefreshTask(domain.getFullyQualifiedDomainName());
|
||||
logger.infofmt("Enqueued refresh for domain %s", domain.getFullyQualifiedDomainName());
|
||||
} catch (Throwable t) {
|
||||
logger.severefmt(t, "Error while refreshing DNS for host rename %s", targetHostKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
java/google/registry/flows/contact/ContactCheckFlow.java
Normal file
45
java/google/registry/flows/contact/ContactCheckFlow.java
Normal 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.flows.contact;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.checkResourcesExist;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.flows.ResourceCheckFlow;
|
||||
import com.google.domain.registry.model.contact.ContactCommand.Check;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData.ContactCheck;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData.ContactCheckData;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that checks whether a contact can be provisioned.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCheckFlow.TooManyResourceChecksException}
|
||||
*/
|
||||
public class ContactCheckFlow extends ResourceCheckFlow<ContactResource, Check> {
|
||||
@Override
|
||||
protected CheckData getCheckData() {
|
||||
Set<String> existingIds = checkResourcesExist(resourceClass, targetIds, now);
|
||||
ImmutableList.Builder<ContactCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String id : targetIds) {
|
||||
boolean unused = !existingIds.contains(id);
|
||||
checks.add(ContactCheck.create(unused, id, unused ? null : "In use"));
|
||||
}
|
||||
return ContactCheckData.create(checks.build());
|
||||
}
|
||||
}
|
65
java/google/registry/flows/contact/ContactCreateFlow.java
Normal file
65
java/google/registry/flows/contact/ContactCreateFlow.java
Normal file
|
@ -0,0 +1,65 @@
|
|||
// 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.flows.contact;
|
||||
|
||||
import static com.google.domain.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
|
||||
import static com.google.domain.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
|
||||
import static com.google.domain.registry.model.EppResourceUtils.createContactHostRoid;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.ResourceCreateFlow;
|
||||
import com.google.domain.registry.model.contact.ContactCommand.Create;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.contact.ContactResource.Builder;
|
||||
import com.google.domain.registry.model.eppoutput.CreateData.ContactCreateData;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.ofy.ObjectifyService;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
/**
|
||||
* An EPP flow that creates a new contact resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException}
|
||||
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
|
||||
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
||||
*/
|
||||
public class ContactCreateFlow extends ResourceCreateFlow<ContactResource, Builder, Create> {
|
||||
@Override
|
||||
protected EppOutput getOutput() {
|
||||
return createOutput(Success, ContactCreateData.create(newResource.getContactId(), now));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createFlowRepoId() {
|
||||
return createContactHostRoid(ObjectifyService.allocateId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verifyNewStateIsAllowed() throws EppException {
|
||||
validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo());
|
||||
validateContactAgainstPolicy(newResource);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean storeXmlInHistoryEntry() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.CONTACT_CREATE;
|
||||
}
|
||||
}
|
87
java/google/registry/flows/contact/ContactDeleteFlow.java
Normal file
87
java/google/registry/flows/contact/ContactDeleteFlow.java
Normal file
|
@ -0,0 +1,87 @@
|
|||
// 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.flows.contact;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.queryDomainsUsingResource;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.ResourceAsyncDeleteFlow;
|
||||
import com.google.domain.registry.flows.async.AsyncFlowUtils;
|
||||
import com.google.domain.registry.flows.async.DeleteContactResourceAction;
|
||||
import com.google.domain.registry.flows.async.DeleteEppResourceAction;
|
||||
import com.google.domain.registry.model.contact.ContactCommand.Delete;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.contact.ContactResource.Builder;
|
||||
import com.google.domain.registry.model.domain.DomainBase;
|
||||
import com.google.domain.registry.model.domain.ReferenceUnion;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
|
||||
/**
|
||||
* An EPP flow that deletes a contact resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceAsyncDeleteFlow.ResourceToDeleteIsReferencedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
||||
*/
|
||||
public class ContactDeleteFlow extends ResourceAsyncDeleteFlow<ContactResource, Builder, Delete> {
|
||||
|
||||
/** In {@link #isLinkedForFailfast}, check this (arbitrary) number of resources from the query. */
|
||||
private static final int FAILFAST_CHECK_COUNT = 5;
|
||||
|
||||
@Override
|
||||
protected boolean isLinkedForFailfast(final ReferenceUnion<ContactResource> ref) {
|
||||
// Query for the first few linked domains, and if found, actually load them. The query is
|
||||
// eventually consistent and so might be very stale, but the direct load will not be stale,
|
||||
// just non-transactional. If we find at least one actual reference then we can reliably
|
||||
// fail. If we don't find any, we can't trust the query and need to do the full mapreduce.
|
||||
return Iterables.any(
|
||||
ofy().load().keys(
|
||||
queryDomainsUsingResource(
|
||||
ContactResource.class, ref.getLinked(), now, FAILFAST_CHECK_COUNT)).values(),
|
||||
new Predicate<DomainBase>() {
|
||||
@Override
|
||||
public boolean apply(DomainBase domain) {
|
||||
return domain.getReferencedContacts().contains(ref);
|
||||
}});
|
||||
}
|
||||
|
||||
/** Enqueues a contact resource deletion on the mapreduce queue. */
|
||||
@Override
|
||||
protected final void enqueueTasks() throws EppException {
|
||||
AsyncFlowUtils.enqueueMapreduceAction(
|
||||
DeleteContactResourceAction.class,
|
||||
ImmutableMap.of(
|
||||
DeleteEppResourceAction.PARAM_RESOURCE_KEY,
|
||||
Key.create(existingResource).getString(),
|
||||
DeleteEppResourceAction.PARAM_REQUESTING_CLIENT_ID,
|
||||
getClientId(),
|
||||
DeleteEppResourceAction.PARAM_IS_SUPERUSER,
|
||||
Boolean.toString(superuser)),
|
||||
RegistryEnvironment.get().config().getAsyncDeleteFlowMapreduceDelay());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.CONTACT_PENDING_DELETE;
|
||||
}
|
||||
}
|
77
java/google/registry/flows/contact/ContactFlowUtils.java
Normal file
77
java/google/registry/flows/contact/ContactFlowUtils.java
Normal 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.flows.contact;
|
||||
|
||||
import static com.google.domain.registry.model.contact.PostalInfo.Type.INTERNATIONALIZED;
|
||||
|
||||
import com.google.common.base.CharMatcher;
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import com.google.domain.registry.model.contact.ContactAddress;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.contact.PostalInfo;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/** Static utility functions for contact flows. */
|
||||
public class ContactFlowUtils {
|
||||
/** Check that an internationalized postal info has only ascii characters. */
|
||||
static void validateAsciiPostalInfo(@Nullable PostalInfo internationalized) throws EppException {
|
||||
if (internationalized != null) {
|
||||
Preconditions.checkState(INTERNATIONALIZED.equals(internationalized.getType()));
|
||||
ContactAddress address = internationalized.getAddress();
|
||||
Set<String> fields = Sets.newHashSet(
|
||||
internationalized.getName(),
|
||||
internationalized.getOrg(),
|
||||
address.getCity(),
|
||||
address.getCountryCode(),
|
||||
address.getState(),
|
||||
address.getZip());
|
||||
fields.addAll(address.getStreet());
|
||||
for (String field : fields) {
|
||||
if (field != null && !CharMatcher.ascii().matchesAllOf(field)) {
|
||||
throw new BadInternationalizedPostalInfoException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Check contact's state against server policy. */
|
||||
static void validateContactAgainstPolicy(ContactResource contact) throws EppException {
|
||||
if (contact.getDisclose() != null && !contact.getDisclose().getFlag()) {
|
||||
throw new DeclineContactDisclosureFieldDisallowedPolicyException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Declining contact disclosure is disallowed by server policy. */
|
||||
static class DeclineContactDisclosureFieldDisallowedPolicyException
|
||||
extends ParameterValuePolicyErrorException {
|
||||
public DeclineContactDisclosureFieldDisallowedPolicyException() {
|
||||
super("Declining contact disclosure is disallowed by server policy.");
|
||||
}
|
||||
}
|
||||
|
||||
/** Internationalized postal infos can only contain ASCII characters. */
|
||||
static class BadInternationalizedPostalInfoException extends ParameterValueSyntaxErrorException {
|
||||
public BadInternationalizedPostalInfoException() {
|
||||
super("Internationalized postal infos can only contain ASCII characters");
|
||||
}
|
||||
}
|
||||
}
|
27
java/google/registry/flows/contact/ContactInfoFlow.java
Normal file
27
java/google/registry/flows/contact/ContactInfoFlow.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
// 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.flows.contact;
|
||||
|
||||
import com.google.domain.registry.flows.ResourceInfoFlow;
|
||||
import com.google.domain.registry.model.contact.ContactCommand.Info;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
|
||||
/**
|
||||
* An EPP flow that reads a contact.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException}
|
||||
*/
|
||||
public class ContactInfoFlow extends ResourceInfoFlow<ContactResource, Info> {}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
// 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.flows.contact;
|
||||
|
||||
import com.google.domain.registry.flows.ResourceTransferApproveFlow;
|
||||
import com.google.domain.registry.model.contact.ContactCommand.Transfer;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.contact.ContactResource.Builder;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
/**
|
||||
* An EPP flow that approves a pending transfer on a {@link ContactResource}.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException}
|
||||
*/
|
||||
public class ContactTransferApproveFlow
|
||||
extends ResourceTransferApproveFlow<ContactResource, Builder, Transfer> {
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.CONTACT_TRANSFER_APPROVE;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// 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.flows.contact;
|
||||
|
||||
import com.google.domain.registry.flows.ResourceTransferCancelFlow;
|
||||
import com.google.domain.registry.model.contact.ContactCommand.Transfer;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.contact.ContactResource.Builder;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
/**
|
||||
* An EPP flow that cancels a pending transfer on a {@link ContactResource}.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferCancelFlow.NotTransferInitiatorException}
|
||||
*/
|
||||
public class ContactTransferCancelFlow
|
||||
extends ResourceTransferCancelFlow<ContactResource, Builder, Transfer> {
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.CONTACT_TRANSFER_CANCEL;
|
||||
}
|
||||
}
|
|
@ -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.flows.contact;
|
||||
|
||||
import com.google.domain.registry.flows.ResourceTransferQueryFlow;
|
||||
import com.google.domain.registry.model.contact.ContactCommand.Transfer;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
|
||||
/**
|
||||
* An EPP flow that queries a pending transfer on a {@link ContactResource}.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferQueryFlow.NoTransferHistoryToQueryException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferQueryFlow.NotAuthorizedToViewTransferException}
|
||||
*/
|
||||
public class ContactTransferQueryFlow extends ResourceTransferQueryFlow<ContactResource, Transfer> {
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// 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.flows.contact;
|
||||
|
||||
import com.google.domain.registry.flows.ResourceTransferRejectFlow;
|
||||
import com.google.domain.registry.model.contact.ContactCommand.Transfer;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.contact.ContactResource.Builder;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
/**
|
||||
* An EPP flow that rejects a pending transfer on a {@link ContactResource}.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException}
|
||||
*/
|
||||
public class ContactTransferRejectFlow
|
||||
extends ResourceTransferRejectFlow<ContactResource, Builder, Transfer> {
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.CONTACT_TRANSFER_REJECT;
|
||||
}
|
||||
}
|
|
@ -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.flows.contact;
|
||||
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.flows.ResourceTransferRequestFlow;
|
||||
import com.google.domain.registry.model.contact.ContactCommand.Transfer;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
import org.joda.time.Duration;
|
||||
|
||||
/**
|
||||
* An EPP flow that requests a transfer on a {@link ContactResource}.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferRequestFlow.AlreadyPendingTransferException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferRequestFlow.MissingTransferRequestAuthInfoException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferRequestFlow.ObjectAlreadySponsoredException}
|
||||
*/
|
||||
public class ContactTransferRequestFlow
|
||||
extends ResourceTransferRequestFlow<ContactResource, Transfer> {
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.CONTACT_TRANSFER_REQUEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Duration getAutomaticTransferLength() {
|
||||
return RegistryEnvironment.get().config().getContactAutomaticTransferLength();
|
||||
}
|
||||
}
|
54
java/google/registry/flows/contact/ContactUpdateFlow.java
Normal file
54
java/google/registry/flows/contact/ContactUpdateFlow.java
Normal 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.flows.contact;
|
||||
|
||||
import static com.google.domain.registry.flows.contact.ContactFlowUtils.validateAsciiPostalInfo;
|
||||
import static com.google.domain.registry.flows.contact.ContactFlowUtils.validateContactAgainstPolicy;
|
||||
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.ResourceUpdateFlow;
|
||||
import com.google.domain.registry.model.contact.ContactCommand.Update;
|
||||
import com.google.domain.registry.model.contact.ContactResource;
|
||||
import com.google.domain.registry.model.contact.ContactResource.Builder;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
/**
|
||||
* An EPP flow that updates a contact resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException}
|
||||
* @error {@link com.google.domain.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link ContactFlowUtils.BadInternationalizedPostalInfoException}
|
||||
* @error {@link ContactFlowUtils.DeclineContactDisclosureFieldDisallowedPolicyException}
|
||||
*/
|
||||
public class ContactUpdateFlow extends ResourceUpdateFlow<ContactResource, Builder, Update> {
|
||||
@Override
|
||||
protected void verifyNewUpdatedStateIsAllowed() throws EppException {
|
||||
validateAsciiPostalInfo(newResource.getInternationalizedPostalInfo());
|
||||
validateContactAgainstPolicy(newResource);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean storeXmlInHistoryEntry() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.CONTACT_UPDATE;
|
||||
}
|
||||
}
|
57
java/google/registry/flows/domain/BaseDomainCheckFlow.java
Normal file
57
java/google/registry/flows/domain/BaseDomainCheckFlow.java
Normal file
|
@ -0,0 +1,57 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateDomainName;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.ResourceCheckFlow;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Check;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/** An EPP flow that checks whether a domain can be provisioned. */
|
||||
public abstract class BaseDomainCheckFlow extends ResourceCheckFlow<DomainResource, Check> {
|
||||
|
||||
protected Map<String, InternetDomainName> domainNames;
|
||||
|
||||
@Override
|
||||
protected final void initCheckResourceFlow() throws EppException {
|
||||
ImmutableMap.Builder<String, InternetDomainName> domains = new ImmutableMap.Builder<>();
|
||||
ImmutableSet.Builder<String> tlds = new ImmutableSet.Builder<>();
|
||||
for (String targetId : ImmutableSet.copyOf(targetIds)) {
|
||||
// This validation is moderately expensive, so cache the results for getCheckData to use too.
|
||||
InternetDomainName domainName = validateDomainName(targetId);
|
||||
tlds.add(domainName.parent().toString());
|
||||
validateDomainNameWithIdnTables(domainName);
|
||||
domains.put(targetId, domainName);
|
||||
}
|
||||
for (String tld : tlds.build()) {
|
||||
checkAllowedAccessToTld(getAllowedTlds(), tld);
|
||||
checkRegistryStateForTld(tld);
|
||||
}
|
||||
domainNames = domains.build();
|
||||
initDomainCheckFlow();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void initDomainCheckFlow() throws EppException {}
|
||||
}
|
378
java/google/registry/flows/domain/BaseDomainCreateFlow.java
Normal file
378
java/google/registry/flows/domain/BaseDomainCreateFlow.java
Normal file
|
@ -0,0 +1,378 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateContactsHaveTypes;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateDomainName;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateDomainNameWithIdnTables;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateDsData;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateNameservers;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateNoDuplicateContacts;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresent;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyLaunchPhase;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyNotReserved;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifySignedMarks;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears;
|
||||
import static com.google.domain.registry.model.EppResourceUtils.createDomainRoid;
|
||||
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.model.registry.Registries.findTldForName;
|
||||
import static com.google.domain.registry.model.registry.label.ReservedList.matchesAnchorTenantReservation;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import com.google.domain.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import com.google.domain.registry.flows.EppException.UnimplementedOptionException;
|
||||
import com.google.domain.registry.flows.ResourceCreateFlow;
|
||||
import com.google.domain.registry.model.domain.DomainBase;
|
||||
import com.google.domain.registry.model.domain.DomainBase.Builder;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Create;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.fee.FeeCreateExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCreateExtension;
|
||||
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.domain.rgp.GracePeriodStatus;
|
||||
import com.google.domain.registry.model.domain.secdns.SecDnsCreateExtension;
|
||||
import com.google.domain.registry.model.ofy.ObjectifyService;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.registry.Registry.TldState;
|
||||
import com.google.domain.registry.model.smd.SignedMark;
|
||||
import com.google.domain.registry.model.tmch.ClaimsListShard;
|
||||
|
||||
import com.googlecode.objectify.Work;
|
||||
|
||||
import org.joda.money.Money;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An EPP flow that creates a new domain resource or application.
|
||||
*
|
||||
* @param <R> the resource type being created
|
||||
* @param <B> a builder for the resource
|
||||
*/
|
||||
public abstract class BaseDomainCreateFlow<R extends DomainBase, B extends Builder<R, ?>>
|
||||
extends ResourceCreateFlow<R, B, Create> {
|
||||
|
||||
private SecDnsCreateExtension secDnsCreate;
|
||||
|
||||
protected LaunchCreateExtension launchCreate;
|
||||
protected String domainLabel;
|
||||
protected InternetDomainName domainName;
|
||||
protected String idnTableName;
|
||||
protected FeeCreateExtension feeCreate;
|
||||
protected Money createCost;
|
||||
protected boolean hasSignedMarks;
|
||||
protected SignedMark signedMark;
|
||||
protected boolean isAnchorTenantViaReservation;
|
||||
protected TldState tldState;
|
||||
|
||||
@Override
|
||||
public final void initResourceCreateOrMutateFlow() throws EppException {
|
||||
command = cloneAndLinkReferences(command, now);
|
||||
registerExtensions(SecDnsCreateExtension.class);
|
||||
secDnsCreate = eppInput.getSingleExtension(SecDnsCreateExtension.class);
|
||||
launchCreate = eppInput.getSingleExtension(LaunchCreateExtension.class);
|
||||
feeCreate = eppInput.getSingleExtension(FeeCreateExtension.class);
|
||||
hasSignedMarks = launchCreate != null && !launchCreate.getSignedMarks().isEmpty();
|
||||
initDomainCreateFlow();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
protected String createFlowRepoId() {
|
||||
// The domain name hasn't been validated yet, so if it's invalid, instead of throwing an error,
|
||||
// simply leave the repoId blank (it won't be needed anyway as the flow will fail when
|
||||
// validation fails later).
|
||||
try {
|
||||
Optional<InternetDomainName> tldParsed =
|
||||
findTldForName(InternetDomainName.from(command.getFullyQualifiedDomainName()));
|
||||
return tldParsed.isPresent()
|
||||
? createDomainRoid(ObjectifyService.allocateId(), tldParsed.get().toString())
|
||||
: null;
|
||||
} catch (IllegalArgumentException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Subclasses may override this to do more specific initialization. */
|
||||
protected void initDomainCreateFlow() {}
|
||||
|
||||
/**
|
||||
* Returns the tld of the domain being created.
|
||||
*
|
||||
* <p>Update/delete domain-related flows can simply grab the tld using existingResource.getTld(),
|
||||
* but in the create flows, the resource doesn't exist yet. So we grab it off the domain name
|
||||
* that the flow is attempting to create.
|
||||
*
|
||||
* <p>Note that it's not always safe to call this until after the domain name has been validated
|
||||
* in verifyCreateIsAllowed().
|
||||
*/
|
||||
protected String getTld() {
|
||||
return domainName.parent().toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fail the domain or application create very fast if the domain is already registered.
|
||||
* <p>
|
||||
* Try to load the domain non-transactionally, since this can hit memcache. If we succeed, and the
|
||||
* domain is not in the ADD grace period (the only state that allows instantaneous transition to
|
||||
* being deleted), we can assume that the domain will not be deleted (and therefore won't be
|
||||
* creatable) until its deletion time. For repeated failed creates this means we can avoid the
|
||||
* datastore lookup, which is very expensive (and first-seen failed creates are no worse than they
|
||||
* otherwise would be). This comes at the cost of the extra lookup for successful creates (or
|
||||
* rather, those that don't fail due to the domain existing) and also for failed creates within
|
||||
* the existing domain's ADD grace period.
|
||||
*/
|
||||
@Override
|
||||
protected final void failfast() throws EppException {
|
||||
// Enter a transactionless context briefly.
|
||||
DomainResource domain = ofy().doTransactionless(new Work<DomainResource>() {
|
||||
@Override
|
||||
public DomainResource run() {
|
||||
// This is cacheable because we are outside of a transaction.
|
||||
return loadByUniqueId(DomainResource.class, targetId, now);
|
||||
}});
|
||||
// If the domain exists already and isn't in the ADD grace period then there is no way it will
|
||||
// be suddenly deleted and therefore the create must fail.
|
||||
if (domain != null
|
||||
&& !domain.getGracePeriodStatuses().contains(GracePeriodStatus.ADD)) {
|
||||
throw new ResourceAlreadyExistsException(targetId, true);
|
||||
}
|
||||
}
|
||||
|
||||
/** Fail if the create command is somehow invalid. */
|
||||
@Override
|
||||
protected final void verifyCreateIsAllowed() throws EppException {
|
||||
// Validate that this is actually a legal domain name on a TLD that the registrar has access to.
|
||||
domainName = validateDomainName(command.getFullyQualifiedDomainName());
|
||||
idnTableName = validateDomainNameWithIdnTables(domainName);
|
||||
String tld = getTld();
|
||||
checkAllowedAccessToTld(getAllowedTlds(), tld);
|
||||
Registry registry = Registry.get(tld);
|
||||
tldState = registry.getTldState(now);
|
||||
checkRegistryStateForTld(tld);
|
||||
domainLabel = domainName.parts().get(0);
|
||||
createCost = registry.getDomainCreateCost(targetId, command.getPeriod().getValue());
|
||||
// The TLD should always be the parent of the requested domain name.
|
||||
isAnchorTenantViaReservation = matchesAnchorTenantReservation(
|
||||
domainLabel, tld, command.getAuthInfo().getPw().getValue());
|
||||
// Superusers can create reserved domains, force creations on domains that require a claims
|
||||
// notice without specifying a claims key, and override blocks on registering premium domains.
|
||||
if (!superuser) {
|
||||
boolean isSunriseApplication =
|
||||
launchCreate != null && !launchCreate.getSignedMarks().isEmpty();
|
||||
if (!isAnchorTenantViaReservation) {
|
||||
verifyNotReserved(domainName, isSunriseApplication);
|
||||
}
|
||||
boolean isClaimsPeriod = now.isBefore(registry.getClaimsPeriodEnd());
|
||||
boolean isClaimsCreate = launchCreate != null && launchCreate.getNotice() != null;
|
||||
if (isClaimsPeriod) {
|
||||
boolean labelOnClaimsList = ClaimsListShard.get().getClaimKey(domainLabel) != null;
|
||||
if (labelOnClaimsList && !isSunriseApplication && !isClaimsCreate) {
|
||||
throw new MissingClaimsNoticeException(domainName.toString());
|
||||
}
|
||||
if (!labelOnClaimsList && isClaimsCreate) {
|
||||
throw new UnexpectedClaimsNoticeException(domainName.toString());
|
||||
}
|
||||
} else if (isClaimsCreate) {
|
||||
throw new ClaimsPeriodEndedException(tld);
|
||||
}
|
||||
verifyPremiumNameIsNotBlocked(targetId, tld, getClientId());
|
||||
}
|
||||
verifyUnitIsYears(command.getPeriod());
|
||||
verifyNotInPendingDelete(
|
||||
command.getContacts(),
|
||||
command.getRegistrant(),
|
||||
command.getNameservers());
|
||||
validateContactsHaveTypes(command.getContacts());
|
||||
validateRegistrantAllowedOnTld(tld, command.getRegistrant());
|
||||
validateNoDuplicateContacts(command.getContacts());
|
||||
validateRequiredContactsPresent(command.getRegistrant(), command.getContacts());
|
||||
validateNameservers(tld, command.getNameservers());
|
||||
validateLaunchCreateExtension();
|
||||
// If a signed mark was provided, then it must match the desired domain label.
|
||||
// We do this after validating the launch create extension so that flows which don't allow any
|
||||
// signed marks throw a more useful error message rather than complaining about specific issues
|
||||
// with the signed marks.
|
||||
if (hasSignedMarks) {
|
||||
signedMark = verifySignedMarks(launchCreate.getSignedMarks(), domainLabel, now);
|
||||
}
|
||||
validateSecDnsExtension();
|
||||
verifyDomainCreateIsAllowed();
|
||||
}
|
||||
|
||||
/** Validate the secDNS extension, if present. */
|
||||
private void validateSecDnsExtension() throws EppException {
|
||||
if (secDnsCreate != null) {
|
||||
if (secDnsCreate.getDsData() == null) {
|
||||
throw new DsDataRequiredException();
|
||||
}
|
||||
if (secDnsCreate.getMaxSigLife() != null) {
|
||||
throw new MaxSigLifeNotSupportedException();
|
||||
}
|
||||
validateDsData(secDnsCreate.getDsData());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If a launch create extension was given (always present for application creates, optional for
|
||||
* domain creates) then validate it.
|
||||
*/
|
||||
private void validateLaunchCreateExtension() throws EppException {
|
||||
if (launchCreate == null) {
|
||||
return;
|
||||
}
|
||||
if (!superuser) { // Superusers can ignore the phase.
|
||||
verifyLaunchPhase(getTld(), launchCreate, now);
|
||||
}
|
||||
if (launchCreate.hasCodeMarks()) {
|
||||
throw new UnsupportedMarkTypeException();
|
||||
}
|
||||
validateDomainLaunchCreateExtension();
|
||||
LaunchNotice notice = launchCreate.getNotice();
|
||||
if (notice == null) {
|
||||
return;
|
||||
}
|
||||
if (!notice.getNoticeId().getValidatorId().equals("tmch")) {
|
||||
throw new InvalidTrademarkValidatorException();
|
||||
}
|
||||
// Superuser can force domain creations regardless of the current date.
|
||||
if (!superuser) {
|
||||
if (notice.getExpirationTime().isBefore(now)) {
|
||||
throw new ExpiredClaimException();
|
||||
}
|
||||
// An acceptance within the past 48 hours is mandated by the TMCH Functional Spec.
|
||||
if (notice.getAcceptedTime().isBefore(now.minusHours(48))) {
|
||||
throw new AcceptedTooLongAgoException();
|
||||
}
|
||||
}
|
||||
try {
|
||||
notice.validate(domainLabel);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new MalformedTcnIdException();
|
||||
} catch (InvalidChecksumException e) {
|
||||
throw new InvalidTcnIdChecksumException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Subclasses may override this to do more specific checks. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyDomainCreateIsAllowed() throws EppException {}
|
||||
|
||||
/** Subclasses may override this to do more specific validation of the launchCreate extension. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void validateDomainLaunchCreateExtension() throws EppException {}
|
||||
|
||||
/** Handle the secDNS extension */
|
||||
@Override
|
||||
protected final void setCreateProperties(B builder) throws EppException {
|
||||
if (secDnsCreate != null) {
|
||||
builder.setDsData(secDnsCreate.getDsData());
|
||||
}
|
||||
builder.setLaunchNotice(launchCreate == null ? null : launchCreate.getNotice());
|
||||
setDomainCreateProperties(builder);
|
||||
builder.setIdnTableName(idnTableName);
|
||||
}
|
||||
|
||||
protected abstract void setDomainCreateProperties(B builder) throws EppException;
|
||||
|
||||
/** Requested domain requires a claims notice. */
|
||||
static class MissingClaimsNoticeException extends StatusProhibitsOperationException {
|
||||
public MissingClaimsNoticeException(String domainName) {
|
||||
super(String.format("%s requires a claims notice", domainName));
|
||||
}
|
||||
}
|
||||
|
||||
/** Requested domain does not require a claims notice. */
|
||||
static class UnexpectedClaimsNoticeException extends StatusProhibitsOperationException {
|
||||
public UnexpectedClaimsNoticeException(String domainName) {
|
||||
super(String.format("%s does not require a claims notice", domainName));
|
||||
}
|
||||
}
|
||||
|
||||
/** The claims period for this TLD has ended. */
|
||||
static class ClaimsPeriodEndedException extends StatusProhibitsOperationException {
|
||||
public ClaimsPeriodEndedException(String tld) {
|
||||
super(String.format("The claims period for %s has ended", tld));
|
||||
}
|
||||
}
|
||||
|
||||
/** The specified trademark validator is not supported. */
|
||||
static class InvalidTrademarkValidatorException extends ParameterValuePolicyErrorException {
|
||||
public InvalidTrademarkValidatorException() {
|
||||
super("The only supported validationID is 'tmch' for the ICANN Trademark Clearinghouse.");
|
||||
}
|
||||
}
|
||||
|
||||
/** At least one dsData is required when using the secDNS extension. */
|
||||
static class DsDataRequiredException extends ParameterValuePolicyErrorException {
|
||||
public DsDataRequiredException() {
|
||||
super("At least one dsData is required when using the secDNS extension");
|
||||
}
|
||||
}
|
||||
|
||||
/** Only encoded signed marks are supported. */
|
||||
static class UnsupportedMarkTypeException extends ParameterValuePolicyErrorException {
|
||||
public UnsupportedMarkTypeException() {
|
||||
super("Only encoded signed marks are supported");
|
||||
}
|
||||
}
|
||||
|
||||
/** The 'maxSigLife' setting is not supported. */
|
||||
static class MaxSigLifeNotSupportedException extends UnimplementedOptionException {
|
||||
public MaxSigLifeNotSupportedException() {
|
||||
super("The 'maxSigLife' setting is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/** The expiration time specified in the claim notice has elapsed. */
|
||||
static class ExpiredClaimException extends ParameterValueRangeErrorException {
|
||||
public ExpiredClaimException() {
|
||||
super("The expiration time specified in the claim notice has elapsed");
|
||||
}
|
||||
}
|
||||
|
||||
/** The acceptance time specified in the claim notice is more than 48 hours in the past. */
|
||||
static class AcceptedTooLongAgoException extends ParameterValueRangeErrorException {
|
||||
public AcceptedTooLongAgoException() {
|
||||
super("The acceptance time specified in the claim notice is more than 48 hours in the past");
|
||||
}
|
||||
}
|
||||
|
||||
/** The specified TCNID is invalid. */
|
||||
static class MalformedTcnIdException extends ParameterValueSyntaxErrorException {
|
||||
public MalformedTcnIdException() {
|
||||
super("The specified TCNID is malformed");
|
||||
}
|
||||
}
|
||||
|
||||
/** The checksum in the specified TCNID does not validate. */
|
||||
static class InvalidTcnIdChecksumException extends ParameterValueRangeErrorException {
|
||||
public InvalidTcnIdChecksumException() {
|
||||
super("The checksum in the specified TCNID does not validate");
|
||||
}
|
||||
}
|
||||
}
|
51
java/google/registry/flows/domain/BaseDomainInfoFlow.java
Normal file
51
java/google/registry/flows/domain/BaseDomainInfoFlow.java
Normal 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.ResourceInfoFlow;
|
||||
import com.google.domain.registry.model.domain.DomainBase;
|
||||
import com.google.domain.registry.model.domain.DomainBase.Builder;
|
||||
import com.google.domain.registry.model.domain.DomainCommand;
|
||||
import com.google.domain.registry.model.domain.secdns.SecDnsInfoExtension;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
|
||||
/**
|
||||
* An EPP flow that reads a domain resource or application.
|
||||
*
|
||||
* @param <R> the resource type being manipulated
|
||||
* @param <B> a builder for the resource
|
||||
*/
|
||||
public abstract class BaseDomainInfoFlow<R extends DomainBase, B extends Builder<R, B>>
|
||||
extends ResourceInfoFlow<R, DomainCommand.Info> {
|
||||
@Override
|
||||
protected final ImmutableList<ResponseExtension> getResponseExtensions() throws EppException {
|
||||
ImmutableList.Builder<ResponseExtension> builder = new ImmutableList.Builder<>();
|
||||
// According to RFC 5910 section 2, we should only return this if the client specified the
|
||||
// "urn:ietf:params:xml:ns:secDNS-1.1" when logging in. However, this is a "SHOULD" not a "MUST"
|
||||
// and we are going to ignore it; clients who don't care about secDNS can just ignore it.
|
||||
if (!existingResource.getDsData().isEmpty()) {
|
||||
builder.add(SecDnsInfoExtension.create(existingResource.getDsData()));
|
||||
}
|
||||
return forceEmptyToNull(builder.addAll(getDomainResponseExtensions()).build());
|
||||
}
|
||||
|
||||
/** Subclasses should override this to add their extensions. */
|
||||
protected abstract ImmutableList<? extends ResponseExtension> getDomainResponseExtensions()
|
||||
throws EppException;
|
||||
}
|
156
java/google/registry/flows/domain/BaseDomainUpdateFlow.java
Normal file
156
java/google/registry/flows/domain/BaseDomainUpdateFlow.java
Normal file
|
@ -0,0 +1,156 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.common.collect.Sets.union;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.cloneAndLinkReferences;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateContactsHaveTypes;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateDsData;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateNameservers;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateNoDuplicateContacts;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateRegistrantAllowedOnTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateRequiredContactsPresent;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyNotInPendingDelete;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import com.google.domain.registry.flows.EppException.RequiredParameterMissingException;
|
||||
import com.google.domain.registry.flows.EppException.UnimplementedOptionException;
|
||||
import com.google.domain.registry.flows.ResourceUpdateFlow;
|
||||
import com.google.domain.registry.model.domain.DomainBase;
|
||||
import com.google.domain.registry.model.domain.DomainBase.Builder;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Update;
|
||||
import com.google.domain.registry.model.domain.secdns.DelegationSignerData;
|
||||
import com.google.domain.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import com.google.domain.registry.model.domain.secdns.SecDnsUpdateExtension.Add;
|
||||
import com.google.domain.registry.model.domain.secdns.SecDnsUpdateExtension.Remove;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that updates a domain application or resource.
|
||||
*
|
||||
* @param <R> the resource type being created
|
||||
* @param <B> a builder for the resource
|
||||
*/
|
||||
public abstract class BaseDomainUpdateFlow<R extends DomainBase, B extends Builder<R, B>>
|
||||
extends ResourceUpdateFlow<R, B, Update> {
|
||||
|
||||
@Override
|
||||
public final void initResourceCreateOrMutateFlow() throws EppException {
|
||||
command = cloneAndLinkReferences(command, now);
|
||||
initDomainUpdateFlow();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
protected void initDomainUpdateFlow() throws EppException {}
|
||||
|
||||
@Override
|
||||
public final B setUpdateProperties(B builder) throws EppException {
|
||||
// Handle the secDNS extension.
|
||||
SecDnsUpdateExtension secDnsUpdate = eppInput.getSingleExtension(SecDnsUpdateExtension.class);
|
||||
if (secDnsUpdate != null) {
|
||||
// We don't support 'urgent' because we do everything as fast as we can anyways.
|
||||
if (Boolean.TRUE.equals(secDnsUpdate.getUrgent())) { // We allow both false and null.
|
||||
throw new UrgentAttributeNotSupportedException();
|
||||
}
|
||||
// There must be at least one of add/rem/chg, and chg isn't actually supported.
|
||||
if (secDnsUpdate.getAdd() == null && secDnsUpdate.getRemove() == null) {
|
||||
// The only thing you can change is maxSigLife, and we don't support that at all.
|
||||
throw (secDnsUpdate.getChange() == null)
|
||||
? new EmptySecDnsUpdateException()
|
||||
: new MaxSigLifeChangeNotSupportedException();
|
||||
}
|
||||
Set<DelegationSignerData> newDsData = existingResource.getDsData();
|
||||
// RFC 5901 specifies that removes are processed before adds.
|
||||
Remove remove = secDnsUpdate.getRemove();
|
||||
if (remove != null) {
|
||||
if (Boolean.FALSE.equals(remove.getAll())) { // Explicit all=false is meaningless.
|
||||
throw new SecDnsAllUsageException();
|
||||
}
|
||||
newDsData = (remove.getAll() == null)
|
||||
? difference(existingResource.getDsData(), remove.getDsData())
|
||||
: ImmutableSet.<DelegationSignerData>of();
|
||||
}
|
||||
Add add = secDnsUpdate.getAdd();
|
||||
if (add != null) {
|
||||
newDsData = union(newDsData, add.getDsData());
|
||||
}
|
||||
builder.setDsData(ImmutableSet.copyOf(newDsData));
|
||||
}
|
||||
return setDomainUpdateProperties(builder);
|
||||
}
|
||||
|
||||
/** Subclasses can override this to do set more specific properties. */
|
||||
protected B setDomainUpdateProperties(B builder) {
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyUpdateIsAllowed() throws EppException {
|
||||
checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld());
|
||||
verifyDomainUpdateIsAllowed();
|
||||
verifyNotInPendingDelete(
|
||||
command.getInnerAdd().getContacts(),
|
||||
command.getInnerChange().getRegistrant(),
|
||||
command.getInnerAdd().getNameservers());
|
||||
validateContactsHaveTypes(command.getInnerAdd().getContacts());
|
||||
validateContactsHaveTypes(command.getInnerRemove().getContacts());
|
||||
}
|
||||
|
||||
/** Subclasses can override this to do more specific verification. */
|
||||
@SuppressWarnings("unused")
|
||||
protected void verifyDomainUpdateIsAllowed() throws EppException {}
|
||||
|
||||
@Override
|
||||
protected final void verifyNewUpdatedStateIsAllowed() throws EppException {
|
||||
validateNoDuplicateContacts(newResource.getContacts());
|
||||
validateRequiredContactsPresent(newResource.getRegistrant(), newResource.getContacts());
|
||||
validateDsData(newResource.getDsData());
|
||||
validateRegistrantAllowedOnTld(newResource.getTld(), newResource.getRegistrant());
|
||||
validateNameservers(newResource.getTld(), newResource.getNameservers());
|
||||
}
|
||||
|
||||
/** The secDNS:all element must have value 'true' if present. */
|
||||
static class SecDnsAllUsageException extends ParameterValuePolicyErrorException {
|
||||
public SecDnsAllUsageException() {
|
||||
super("The secDNS:all element must have value 'true' if present");
|
||||
}
|
||||
}
|
||||
|
||||
/** At least one of 'add' or 'rem' is required on a secDNS update. */
|
||||
static class EmptySecDnsUpdateException extends RequiredParameterMissingException {
|
||||
public EmptySecDnsUpdateException() {
|
||||
super("At least one of 'add' or 'rem' is required on a secDNS update");
|
||||
}
|
||||
}
|
||||
|
||||
/** The 'urgent' attribute is not supported. */
|
||||
static class UrgentAttributeNotSupportedException extends UnimplementedOptionException {
|
||||
public UrgentAttributeNotSupportedException() {
|
||||
super("The 'urgent' attribute is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/** Changing 'maxSigLife' is not supported. */
|
||||
static class MaxSigLifeChangeNotSupportedException extends UnimplementedOptionException {
|
||||
public MaxSigLifeChangeNotSupportedException() {
|
||||
super("Changing 'maxSigLife' is not supported");
|
||||
}
|
||||
}
|
||||
}
|
80
java/google/registry/flows/domain/ClaimsCheckFlow.java
Normal file
80
java/google/registry/flows/domain/ClaimsCheckFlow.java
Normal 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.model.domain.launch.LaunchPhase.CLAIMS;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.isAtOrAfter;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCheckExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCheckResponseExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCheckResponseExtension.LaunchCheck;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCheckResponseExtension.LaunchCheckName;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.registry.Registry.TldState;
|
||||
import com.google.domain.registry.model.tmch.ClaimsListShard;
|
||||
|
||||
import java.util.Map.Entry;
|
||||
|
||||
/**
|
||||
* An EPP flow that checks whether strings are trademarked.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCheckFlow.TooManyResourceChecksException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlow.BadCommandForRegistryPhaseException}
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link DomainFlowUtils.TldDoesNotExistException}
|
||||
*/
|
||||
public class ClaimsCheckFlow extends BaseDomainCheckFlow {
|
||||
|
||||
public static final ImmutableSet<TldState> DISALLOWED_TLD_STATES = Sets.immutableEnumSet(
|
||||
TldState.PREDELEGATION, TldState.SUNRISE);
|
||||
|
||||
@Override
|
||||
protected void initDomainCheckFlow() throws EppException {
|
||||
registerExtensions(LaunchCheckExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CheckData getCheckData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImmutableList<? extends ResponseExtension> getResponseExtensions() throws EppException {
|
||||
ImmutableList.Builder<LaunchCheck> launchChecksBuilder = new ImmutableList.Builder<>();
|
||||
for (Entry<String, InternetDomainName> entry : domainNames.entrySet()) {
|
||||
InternetDomainName domainName = entry.getValue();
|
||||
if (isAtOrAfter(now, Registry.get(domainName.parent().toString()).getClaimsPeriodEnd())) {
|
||||
throw new BadCommandForRegistryPhaseException();
|
||||
}
|
||||
String claimKey = ClaimsListShard.get().getClaimKey(domainName.parts().get(0));
|
||||
launchChecksBuilder.add(LaunchCheck.create(
|
||||
LaunchCheckName.create(claimKey != null, entry.getKey()), claimKey));
|
||||
}
|
||||
return ImmutableList.of(
|
||||
LaunchCheckResponseExtension.create(CLAIMS, launchChecksBuilder.build()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ImmutableSet<TldState> getDisallowedTldStates() {
|
||||
return DISALLOWED_TLD_STATES;
|
||||
}
|
||||
}
|
223
java/google/registry/flows/domain/DomainAllocateFlow.java
Normal file
223
java/google/registry/flows/domain/DomainAllocateFlow.java
Normal 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.flows.domain;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.getReservationType;
|
||||
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.AuthorizationErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ObjectDoesNotExistException;
|
||||
import com.google.domain.registry.flows.EppException.RequiredParameterMissingException;
|
||||
import com.google.domain.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import com.google.domain.registry.model.billing.BillingEvent;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Flag;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Reason;
|
||||
import com.google.domain.registry.model.domain.DomainApplication;
|
||||
import com.google.domain.registry.model.domain.DomainResource.Builder;
|
||||
import com.google.domain.registry.model.domain.GracePeriod;
|
||||
import com.google.domain.registry.model.domain.allocate.AllocateCreateExtension;
|
||||
import com.google.domain.registry.model.domain.launch.ApplicationStatus;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchInfoResponseExtension;
|
||||
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.registry.label.ReservationType;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.tmch.LordnTask;
|
||||
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
/**
|
||||
* An EPP flow that allocates a new domain resource from a domain application.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.EppException.UnimplementedExtensionException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException}
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link DomainAllocateFlow.HasFinalStatusException}
|
||||
* @error {@link DomainAllocateFlow.MissingAllocateCreateExtensionException}
|
||||
* @error {@link DomainAllocateFlow.MissingApplicationException}
|
||||
* @error {@link DomainAllocateFlow.OnlySuperuserCanAllocateException}
|
||||
*/
|
||||
public class DomainAllocateFlow extends DomainCreateOrAllocateFlow {
|
||||
|
||||
protected AllocateCreateExtension allocateCreate;
|
||||
protected DomainApplication application;
|
||||
|
||||
@Override
|
||||
protected final void initDomainCreateOrAllocateFlow() {
|
||||
registerExtensions(AllocateCreateExtension.class);
|
||||
allocateCreate = eppInput.getSingleExtension(AllocateCreateExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyDomainCreateIsAllowed() throws EppException {
|
||||
if (!superuser) {
|
||||
throw new OnlySuperuserCanAllocateException();
|
||||
}
|
||||
if (allocateCreate == null) {
|
||||
throw new MissingAllocateCreateExtensionException();
|
||||
}
|
||||
String applicationRoid = allocateCreate.getApplicationRoid();
|
||||
application = loadByUniqueId(DomainApplication.class, applicationRoid, now);
|
||||
if (application == null) {
|
||||
throw new MissingApplicationException(applicationRoid);
|
||||
}
|
||||
if (application.getApplicationStatus().isFinalStatus()) {
|
||||
throw new HasFinalStatusException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void setDomainCreateOrAllocateProperties(Builder builder) {
|
||||
boolean sunrushAddGracePeriod = isNullOrEmpty(command.getNameservers());
|
||||
Registry registry = Registry.get(getTld());
|
||||
ImmutableSet.Builder<Flag> billingFlagsBuilder = ImmutableSet.builder();
|
||||
if (!application.getEncodedSignedMarks().isEmpty()) {
|
||||
billingFlagsBuilder.add(Flag.SUNRISE);
|
||||
} else {
|
||||
billingFlagsBuilder.add(Flag.LANDRUSH);
|
||||
}
|
||||
BillingEvent.OneTime billingEvent = new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.CREATE)
|
||||
.setFlags(billingFlagsBuilder.add(Flag.ALLOCATION).build())
|
||||
.setTargetId(targetId)
|
||||
.setClientId(getClientId())
|
||||
.setCost(registry.getDomainCreateCost(targetId, command.getPeriod().getValue()))
|
||||
.setPeriodYears(command.getPeriod().getValue())
|
||||
.setEventTime(now)
|
||||
// If there are no nameservers on the domain, then they get the benefit of the sunrush add
|
||||
// grace period, which is longer than the standard add grace period.
|
||||
.setBillingTime(now.plus(sunrushAddGracePeriod
|
||||
? registry.getSunrushAddGracePeriodLength()
|
||||
: registry.getAddGracePeriodLength()))
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
ReservationType reservationType = getReservationType(domainName);
|
||||
ofy().save().<Object>entities(
|
||||
// Save the billing event
|
||||
billingEvent,
|
||||
// Update the application itself.
|
||||
application.asBuilder()
|
||||
.setApplicationStatus(ApplicationStatus.ALLOCATED)
|
||||
.removeStatusValue(StatusValue.PENDING_CREATE)
|
||||
.build(),
|
||||
// Create a poll message informing the registrar that the application status was updated.
|
||||
new PollMessage.OneTime.Builder()
|
||||
.setClientId(application.getCurrentSponsorClientId())
|
||||
.setEventTime(ofy().getTransactionTime())
|
||||
.setMsg(reservationType == ReservationType.NAME_COLLISION
|
||||
// Change the poll message to remind the registrar of the name collision policy.
|
||||
? "Domain on the name collision list was allocated. "
|
||||
+ "But by policy, the domain will not be delegated. "
|
||||
+ "Please visit https://www.icann.org/namecollision "
|
||||
+ "for more information on name collision."
|
||||
: "Domain was allocated")
|
||||
.setResponseData(ImmutableList.of(
|
||||
DomainPendingActionNotificationResponse.create(
|
||||
application.getFullyQualifiedDomainName(),
|
||||
true,
|
||||
// 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.
|
||||
application.getCreationTrid() == null
|
||||
? checkNotNull(ofy()
|
||||
.load()
|
||||
.type(HistoryEntry.class)
|
||||
.ancestor(application)
|
||||
.order("modificationTime")
|
||||
.first()
|
||||
.now()
|
||||
.getTrid())
|
||||
: application.getCreationTrid(),
|
||||
now)))
|
||||
.setResponseExtensions(ImmutableList.of(
|
||||
new LaunchInfoResponseExtension.Builder()
|
||||
.setApplicationId(application.getForeignKey())
|
||||
.setPhase(application.getPhase())
|
||||
.setApplicationStatus(ApplicationStatus.ALLOCATED)
|
||||
.build()))
|
||||
.setParent(historyEntry)
|
||||
.build(),
|
||||
// Create a history entry (with no xml or trid) to record that we updated the application.
|
||||
new HistoryEntry.Builder()
|
||||
.setType(HistoryEntry.Type.DOMAIN_APPLICATION_STATUS_UPDATE)
|
||||
.setParent(application)
|
||||
.setModificationTime(now)
|
||||
.setClientId(application.getCurrentSponsorClientId())
|
||||
.setBySuperuser(true)
|
||||
.build());
|
||||
// Set the properties on the new domain.
|
||||
builder
|
||||
.addGracePeriod(GracePeriod.forBillingEvent(
|
||||
sunrushAddGracePeriod ? GracePeriodStatus.SUNRUSH_ADD : GracePeriodStatus.ADD,
|
||||
billingEvent))
|
||||
.setApplicationTime(allocateCreate.getApplicationTime())
|
||||
.setApplication(Ref.create(application))
|
||||
.setSmdId(allocateCreate.getSmdId())
|
||||
.setLaunchNotice(allocateCreate.getNotice());
|
||||
// Names on the collision list will not be delegated. Set server hold.
|
||||
if (ReservationType.NAME_COLLISION == reservationType) {
|
||||
builder.addStatusValue(StatusValue.SERVER_HOLD);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enqueueLordnTaskIfNeeded() {
|
||||
if (allocateCreate.getSmdId() != null || allocateCreate.getNotice() != null) {
|
||||
LordnTask.enqueueDomainResourceTask(newResource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_ALLOCATE;
|
||||
}
|
||||
|
||||
/** The allocate create extension is required to allocate a domain. */
|
||||
static class MissingAllocateCreateExtensionException extends RequiredParameterMissingException {
|
||||
public MissingAllocateCreateExtensionException() {
|
||||
super("The allocate create extension is required to allocate a domain");
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain application with specific ROID does not exist. */
|
||||
static class MissingApplicationException extends ObjectDoesNotExistException {
|
||||
public MissingApplicationException(String applicationRoid) {
|
||||
super(DomainApplication.class, applicationRoid);
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain application already has a final status. */
|
||||
static class HasFinalStatusException extends StatusProhibitsOperationException {
|
||||
public HasFinalStatusException() {
|
||||
super("Domain application already has a final status");
|
||||
}
|
||||
}
|
||||
|
||||
/** Only a superuser can allocate domains. */
|
||||
static class OnlySuperuserCanAllocateException extends AuthorizationErrorException {
|
||||
public OnlySuperuserCanAllocateException() {
|
||||
super("Only a superuser can allocate domains");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,253 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.DISALLOWED_TLD_STATES_FOR_LAUNCH_FLOWS;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
import static com.google.domain.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
|
||||
import static com.google.domain.registry.model.index.ForeignKeyIndex.loadAndGetReference;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.CommandUseErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ObjectAlreadyExistsException;
|
||||
import com.google.domain.registry.flows.EppException.RequiredParameterMissingException;
|
||||
import com.google.domain.registry.model.domain.DomainApplication;
|
||||
import com.google.domain.registry.model.domain.DomainApplication.Builder;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.Period;
|
||||
import com.google.domain.registry.model.domain.fee.Fee;
|
||||
import com.google.domain.registry.model.domain.fee.FeeCreateExtension;
|
||||
import com.google.domain.registry.model.domain.fee.FeeCreateResponseExtension;
|
||||
import com.google.domain.registry.model.domain.launch.ApplicationStatus;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCreateExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCreateResponseExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchPhase;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppoutput.CreateData.DomainCreateData;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
import com.google.domain.registry.model.registry.Registry.TldState;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.model.smd.AbstractSignedMark;
|
||||
import com.google.domain.registry.model.smd.EncodedSignedMark;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An EPP flow that creates a new application for a domain resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.EppException.UnimplementedExtensionException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlow.BadCommandForRegistryPhaseException}
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException}
|
||||
* @error {@link BaseDomainCreateFlow.AcceptedTooLongAgoException}
|
||||
* @error {@link BaseDomainCreateFlow.ClaimsPeriodEndedException}
|
||||
* @error {@link BaseDomainCreateFlow.ExpiredClaimException}
|
||||
* @error {@link BaseDomainCreateFlow.InvalidTcnIdChecksumException}
|
||||
* @error {@link BaseDomainCreateFlow.InvalidTrademarkValidatorException}
|
||||
* @error {@link BaseDomainCreateFlow.MalformedTcnIdException}
|
||||
* @error {@link BaseDomainCreateFlow.MaxSigLifeNotSupportedException}
|
||||
* @error {@link BaseDomainCreateFlow.MissingClaimsNoticeException}
|
||||
* @error {@link BaseDomainCreateFlow.UnexpectedClaimsNoticeException}
|
||||
* @error {@link BaseDomainCreateFlow.UnsupportedMarkTypeException}
|
||||
* @error {@link DomainApplicationCreateFlow.LandrushApplicationDisallowedDuringSunriseException}
|
||||
* @error {@link DomainApplicationCreateFlow.NoticeCannotBeUsedWithSignedMarkException}
|
||||
* @error {@link DomainApplicationCreateFlow.SunriseApplicationDisallowedDuringLandrushException}
|
||||
* @error {@link DomainApplicationCreateFlow.UncontestedSunriseApplicationBlockedInLandrushException}
|
||||
* @error {@link DomainFlowUtils.BadDomainNameCharacterException}
|
||||
* @error {@link DomainFlowUtils.BadDomainNamePartsCountException}
|
||||
* @error {@link DomainFlowUtils.BadPeriodUnitException}
|
||||
* @error {@link DomainFlowUtils.Base64RequiredForEncodedSignedMarksException}
|
||||
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
|
||||
* @error {@link DomainFlowUtils.CurrencyValueScaleException}
|
||||
* @error {@link DomainFlowUtils.DashesInThirdAndFourthException}
|
||||
* @error {@link DomainFlowUtils.DomainLabelTooLongException}
|
||||
* @error {@link DomainFlowUtils.DomainReservedException}
|
||||
* @error {@link DomainFlowUtils.DuplicateContactForRoleException}
|
||||
* @error {@link DomainFlowUtils.EmptyDomainNamePartException}
|
||||
* @error {@link DomainFlowUtils.FeesMismatchException}
|
||||
* @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException}
|
||||
* @error {@link DomainFlowUtils.InvalidIdnDomainLabelException}
|
||||
* @error {@link DomainFlowUtils.InvalidPunycodeException}
|
||||
* @error {@link DomainFlowUtils.LaunchPhaseMismatchException}
|
||||
* @error {@link DomainFlowUtils.LeadingDashException}
|
||||
* @error {@link DomainFlowUtils.LinkedResourceDoesNotExistException}
|
||||
* @error {@link DomainFlowUtils.MissingContactTypeException}
|
||||
* @error {@link DomainFlowUtils.NameserverNotAllowedException}
|
||||
* @error {@link DomainFlowUtils.NoMarksFoundMatchingDomainException}
|
||||
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
|
||||
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
|
||||
* @error {@link DomainFlowUtils.SignedMarksMustBeEncodedException}
|
||||
* @error {@link DomainFlowUtils.SignedMarkCertificateExpiredException}
|
||||
* @error {@link DomainFlowUtils.SignedMarkCertificateInvalidException}
|
||||
* @error {@link DomainFlowUtils.SignedMarkCertificateNotYetValidException}
|
||||
* @error {@link DomainFlowUtils.SignedMarkCertificateRevokedException}
|
||||
* @error {@link DomainFlowUtils.SignedMarkCertificateSignatureException}
|
||||
* @error {@link DomainFlowUtils.SignedMarkEncodingErrorException}
|
||||
* @error {@link DomainFlowUtils.SignedMarkParsingErrorException}
|
||||
* @error {@link DomainFlowUtils.SignedMarkRevokedErrorException}
|
||||
* @error {@link DomainFlowUtils.SignedMarkSignatureException}
|
||||
* @error {@link DomainFlowUtils.TldDoesNotExistException}
|
||||
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
|
||||
* @error {@link DomainFlowUtils.TooManyNameserversException}
|
||||
* @error {@link DomainFlowUtils.TooManySignedMarksException}
|
||||
* @error {@link DomainFlowUtils.TrailingDashException}
|
||||
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
|
||||
*/
|
||||
public class DomainApplicationCreateFlow extends BaseDomainCreateFlow<DomainApplication, Builder> {
|
||||
|
||||
@Override
|
||||
protected void initDomainCreateFlow() {
|
||||
registerExtensions(FeeCreateExtension.class, LaunchCreateExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateDomainLaunchCreateExtension() throws EppException {
|
||||
if (launchCreate.getSignedMarks().isEmpty()) {
|
||||
// During sunrise, a signed mark is required since only trademark holders are allowed to
|
||||
// create an application. However, we found no marks (ie, this was a landrush application).
|
||||
if (tldState == TldState.SUNRISE) {
|
||||
throw new LandrushApplicationDisallowedDuringSunriseException();
|
||||
}
|
||||
} else {
|
||||
if (launchCreate.getNotice() != null) { // Can't use a claims notice id with a signed mark.
|
||||
throw new NoticeCannotBeUsedWithSignedMarkException();
|
||||
}
|
||||
if (tldState == TldState.LANDRUSH) {
|
||||
throw new SunriseApplicationDisallowedDuringLandrushException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verifyDomainCreateIsAllowed() throws EppException {
|
||||
validateFeeChallenge(targetId, getTld(), feeCreate, createCost);
|
||||
if (tldState == TldState.LANDRUSH && !superuser) {
|
||||
// Prohibit creating a landrush application in LANDRUSH (but not in SUNRUSH) if there is
|
||||
// exactly one sunrise application for the same name.
|
||||
List<DomainApplication> applications = FluentIterable
|
||||
.from(loadActiveApplicationsByDomainName(targetId, now))
|
||||
.limit(2)
|
||||
.toList();
|
||||
if (applications.size() == 1 && applications.get(0).getPhase().equals(LaunchPhase.SUNRISE)) {
|
||||
throw new UncontestedSunriseApplicationBlockedInLandrushException();
|
||||
}
|
||||
}
|
||||
// Fail if the domain is already registered (e.g. this is a landrush application but the domain
|
||||
// was awarded at the end of sunrise).
|
||||
if (loadAndGetReference(DomainResource.class, targetId, now) != null) {
|
||||
throw new ResourceAlreadyExistsException(targetId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setDomainCreateProperties(Builder builder) {
|
||||
builder
|
||||
.setCreationTrid(trid)
|
||||
.setPhase(launchCreate.getPhase())
|
||||
.setApplicationStatus(ApplicationStatus.VALIDATED)
|
||||
.addStatusValue(StatusValue.PENDING_CREATE);
|
||||
if (!launchCreate.getSignedMarks().isEmpty()) {
|
||||
builder.setEncodedSignedMarks(FluentIterable
|
||||
.from(launchCreate.getSignedMarks())
|
||||
.transform(new Function<AbstractSignedMark, EncodedSignedMark>() {
|
||||
@Override
|
||||
public EncodedSignedMark apply(AbstractSignedMark abstractSignedMark) {
|
||||
// We verified that this is the case in verifyDomainCreateIsAllowed().
|
||||
return (EncodedSignedMark) abstractSignedMark;
|
||||
}})
|
||||
.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ImmutableSet<TldState> getDisallowedTldStates() {
|
||||
return DISALLOWED_TLD_STATES_FOR_LAUNCH_FLOWS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_APPLICATION_CREATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Period getCommandPeriod() {
|
||||
return command.getPeriod();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean tryToLoadExisting() {
|
||||
// Multiple domain applications can be created for the same targetId (which is the fully
|
||||
// qualified domain name), so don't try to load an existing resource with the same target id.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final EppOutput getOutput() {
|
||||
ImmutableList.Builder<ResponseExtension> responseExtensionsBuilder =
|
||||
new ImmutableList.Builder<>();
|
||||
responseExtensionsBuilder.add(new LaunchCreateResponseExtension.Builder()
|
||||
.setPhase(launchCreate.getPhase())
|
||||
.setApplicationId(newResource.getForeignKey())
|
||||
.build());
|
||||
if (feeCreate != null) {
|
||||
responseExtensionsBuilder.add(new FeeCreateResponseExtension.Builder()
|
||||
.setCurrency(createCost.getCurrencyUnit())
|
||||
.setFee(ImmutableList.of(Fee.create(createCost.getAmount(), "create")))
|
||||
.build());
|
||||
}
|
||||
|
||||
return createOutput(
|
||||
Success,
|
||||
DomainCreateData.create(newResource.getFullyQualifiedDomainName(), now, null),
|
||||
responseExtensionsBuilder.build());
|
||||
}
|
||||
|
||||
/** Landrush applications are disallowed during sunrise. */
|
||||
static class LandrushApplicationDisallowedDuringSunriseException
|
||||
extends RequiredParameterMissingException {
|
||||
public LandrushApplicationDisallowedDuringSunriseException() {
|
||||
super("Landrush applications are disallowed during sunrise");
|
||||
}
|
||||
}
|
||||
|
||||
/** A notice cannot be specified when using a signed mark. */
|
||||
static class NoticeCannotBeUsedWithSignedMarkException extends CommandUseErrorException {
|
||||
public NoticeCannotBeUsedWithSignedMarkException() {
|
||||
super("A notice cannot be specified when using a signed mark");
|
||||
}
|
||||
}
|
||||
|
||||
/** Sunrise applications are disallowed during landrush. */
|
||||
static class SunriseApplicationDisallowedDuringLandrushException
|
||||
extends CommandUseErrorException {
|
||||
public SunriseApplicationDisallowedDuringLandrushException() {
|
||||
super("Sunrise applications are disallowed during landrush");
|
||||
}
|
||||
}
|
||||
|
||||
/** This name has already been claimed by a sunrise applicant. */
|
||||
static class UncontestedSunriseApplicationBlockedInLandrushException
|
||||
extends ObjectAlreadyExistsException {
|
||||
public UncontestedSunriseApplicationBlockedInLandrushException() {
|
||||
super("This name has already been claimed by a sunrise applicant");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.DISALLOWED_TLD_STATES_FOR_LAUNCH_FLOWS;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyLaunchApplicationIdMatchesDomain;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyLaunchPhase;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import com.google.domain.registry.flows.ResourceSyncDeleteFlow;
|
||||
import com.google.domain.registry.model.domain.DomainApplication;
|
||||
import com.google.domain.registry.model.domain.DomainApplication.Builder;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Delete;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchDeleteExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchPhase;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.registry.Registry.TldState;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that deletes a domain application.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.EppException.UnimplementedExtensionException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlow.BadCommandForRegistryPhaseException}
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link DomainApplicationDeleteFlow.SunriseApplicationCannotBeDeletedInLandrushException}
|
||||
* @error {@link DomainFlowUtils.ApplicationDomainNameMismatchException}
|
||||
* @error {@link DomainFlowUtils.LaunchPhaseMismatchException}
|
||||
*/
|
||||
public class DomainApplicationDeleteFlow
|
||||
extends ResourceSyncDeleteFlow<DomainApplication, Builder, Delete> {
|
||||
|
||||
@Override
|
||||
protected void initResourceCreateOrMutateFlow() throws EppException {
|
||||
registerExtensions(LaunchDeleteExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verifyMutationOnOwnedResourceAllowed() throws EppException {
|
||||
String tld = existingResource.getTld();
|
||||
checkRegistryStateForTld(tld);
|
||||
checkAllowedAccessToTld(getAllowedTlds(), tld);
|
||||
verifyLaunchPhase(tld, eppInput.getSingleExtension(LaunchDeleteExtension.class), now);
|
||||
verifyLaunchApplicationIdMatchesDomain(command, existingResource);
|
||||
// Don't allow deleting a sunrise application during landrush.
|
||||
if (existingResource.getPhase().equals(LaunchPhase.SUNRISE)
|
||||
&& Registry.get(existingResource.getTld()).getTldState(now).equals(TldState.LANDRUSH)
|
||||
&& !superuser) {
|
||||
throw new SunriseApplicationCannotBeDeletedInLandrushException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ImmutableSet<TldState> getDisallowedTldStates() {
|
||||
return DISALLOWED_TLD_STATES_FOR_LAUNCH_FLOWS;
|
||||
}
|
||||
|
||||
/** Domain applications do not respect status values that prohibit various operations. */
|
||||
@Override
|
||||
protected Set<StatusValue> getDisallowedStatuses() {
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_APPLICATION_DELETE;
|
||||
}
|
||||
|
||||
/** A sunrise application cannot be deleted during landrush. */
|
||||
static class SunriseApplicationCannotBeDeletedInLandrushException
|
||||
extends StatusProhibitsOperationException {
|
||||
public SunriseApplicationCannotBeDeletedInLandrushException() {
|
||||
super("A sunrise application cannot be deleted during landrush");
|
||||
}
|
||||
}
|
||||
}
|
117
java/google/registry/flows/domain/DomainApplicationInfoFlow.java
Normal file
117
java/google/registry/flows/domain/DomainApplicationInfoFlow.java
Normal file
|
@ -0,0 +1,117 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.EppXmlTransformer.unmarshal;
|
||||
import static com.google.domain.registry.flows.ResourceFlowUtils.verifyResourceOwnership;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyLaunchApplicationIdMatchesDomain;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import com.google.domain.registry.flows.EppException.RequiredParameterMissingException;
|
||||
import com.google.domain.registry.model.domain.DomainApplication;
|
||||
import com.google.domain.registry.model.domain.DomainApplication.Builder;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchInfoExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchInfoResponseExtension;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
import com.google.domain.registry.model.mark.Mark;
|
||||
import com.google.domain.registry.model.smd.EncodedSignedMark;
|
||||
import com.google.domain.registry.model.smd.SignedMark;
|
||||
|
||||
/**
|
||||
* An EPP flow that reads a domain application.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException}
|
||||
* @error {@link DomainFlowUtils.ApplicationDomainNameMismatchException}
|
||||
* @error {@link DomainApplicationInfoFlow.ApplicationLaunchPhaseMismatchException}
|
||||
* @error {@link DomainApplicationInfoFlow.MissingApplicationIdException}
|
||||
*/
|
||||
public class DomainApplicationInfoFlow extends BaseDomainInfoFlow<DomainApplication, Builder> {
|
||||
|
||||
private boolean includeMarks;
|
||||
|
||||
@Override
|
||||
protected final void initSingleResourceFlow() throws EppException {
|
||||
registerExtensions(LaunchInfoExtension.class);
|
||||
// We need to do this in init rather than verify or we'll get the generic "object not found".
|
||||
LaunchInfoExtension extension = eppInput.getSingleExtension(LaunchInfoExtension.class);
|
||||
if (extension.getApplicationId() == null) {
|
||||
throw new MissingApplicationIdException();
|
||||
}
|
||||
includeMarks = Boolean.TRUE.equals(extension.getIncludeMark()); // Default to false.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyQueryIsAllowed() throws EppException {
|
||||
verifyLaunchApplicationIdMatchesDomain(command, existingResource);
|
||||
if (!existingResource.getPhase().equals(
|
||||
eppInput.getSingleExtension(LaunchInfoExtension.class).getPhase())) {
|
||||
throw new ApplicationLaunchPhaseMismatchException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final DomainApplication getResourceInfo() throws EppException {
|
||||
// We don't support authInfo for applications, so if it's another registrar always fail.
|
||||
verifyResourceOwnership(getClientId(), existingResource);
|
||||
if (!command.getHostsRequest().requestDelegated()) {
|
||||
// Delegated hosts are present by default, so clear them out if they aren't wanted.
|
||||
// This requires overriding the implicit status values so that we don't get INACTIVE added due
|
||||
// to the missing nameservers.
|
||||
return existingResource.asBuilder()
|
||||
.setNameservers(null)
|
||||
.buildWithoutImplicitStatusValues();
|
||||
}
|
||||
return existingResource;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ImmutableList<? extends ResponseExtension> getDomainResponseExtensions()
|
||||
throws EppException {
|
||||
ImmutableList.Builder<Mark> marksBuilder = new ImmutableList.Builder<>();
|
||||
if (includeMarks) {
|
||||
for (EncodedSignedMark encodedMark : existingResource.getEncodedSignedMarks()) {
|
||||
try {
|
||||
marksBuilder.add(((SignedMark) unmarshal(encodedMark.getBytes())).getMark());
|
||||
} catch (EppException e) {
|
||||
// This is a serious error; don't let the benign EppException propagate.
|
||||
throw new IllegalStateException("Could not decode a stored encoded signed mark");
|
||||
}
|
||||
}
|
||||
}
|
||||
return ImmutableList.of(new LaunchInfoResponseExtension.Builder()
|
||||
.setPhase(existingResource.getPhase())
|
||||
.setApplicationId(existingResource.getForeignKey())
|
||||
.setApplicationStatus(existingResource.getApplicationStatus())
|
||||
.setMarks(marksBuilder.build())
|
||||
.build());
|
||||
}
|
||||
|
||||
/** Application id is required. */
|
||||
static class MissingApplicationIdException extends RequiredParameterMissingException {
|
||||
public MissingApplicationIdException() {
|
||||
super("Application id is required");
|
||||
}
|
||||
}
|
||||
|
||||
/** Declared launch extension phase does not match phase of the application. */
|
||||
static class ApplicationLaunchPhaseMismatchException extends ParameterValuePolicyErrorException {
|
||||
public ApplicationLaunchPhaseMismatchException() {
|
||||
super("Declared launch extension phase does not match the phase of the application");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.common.base.CaseFormat.LOWER_CAMEL;
|
||||
import static com.google.common.base.CaseFormat.UPPER_UNDERSCORE;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.DISALLOWED_TLD_STATES_FOR_LAUNCH_FLOWS;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import com.google.domain.registry.model.domain.DomainApplication;
|
||||
import com.google.domain.registry.model.domain.DomainApplication.Builder;
|
||||
import com.google.domain.registry.model.domain.launch.ApplicationStatus;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchUpdateExtension;
|
||||
import com.google.domain.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import com.google.domain.registry.model.registry.Registry.TldState;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
/**
|
||||
* An EPP flow that updates a domain resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.EppException.UnimplementedExtensionException}
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceUpdateFlow.AddRemoveSameValueEppException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException}
|
||||
* @error {@link com.google.domain.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link BaseDomainUpdateFlow.EmptySecDnsUpdateException}
|
||||
* @error {@link BaseDomainUpdateFlow.MaxSigLifeChangeNotSupportedException}
|
||||
* @error {@link BaseDomainUpdateFlow.SecDnsAllUsageException}
|
||||
* @error {@link BaseDomainUpdateFlow.UrgentAttributeNotSupportedException}
|
||||
* @error {@link DomainFlowUtils.DuplicateContactForRoleException}
|
||||
* @error {@link DomainFlowUtils.LinkedResourceDoesNotExistException}
|
||||
* @error {@link DomainFlowUtils.MissingAdminContactException}
|
||||
* @error {@link DomainFlowUtils.MissingContactTypeException}
|
||||
* @error {@link DomainFlowUtils.MissingTechnicalContactException}
|
||||
* @error {@link DomainFlowUtils.NameserverNotAllowedException}
|
||||
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
|
||||
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
|
||||
* @error {@link DomainFlowUtils.TooManyNameserversException}
|
||||
* @error {@link DomainApplicationUpdateFlow.ApplicationStatusProhibitsUpdateException}
|
||||
*/
|
||||
public class DomainApplicationUpdateFlow
|
||||
extends BaseDomainUpdateFlow<DomainApplication, Builder> {
|
||||
|
||||
@Override
|
||||
protected void initDomainUpdateFlow() throws EppException {
|
||||
registerExtensions(LaunchUpdateExtension.class, SecDnsUpdateExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyDomainUpdateIsAllowed() throws EppException {
|
||||
switch (existingResource.getApplicationStatus()) {
|
||||
case PENDING_ALLOCATION:
|
||||
case PENDING_VALIDATION:
|
||||
case VALIDATED:
|
||||
return;
|
||||
default:
|
||||
throw new ApplicationStatusProhibitsUpdateException(
|
||||
existingResource.getApplicationStatus());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ImmutableSet<TldState> getDisallowedTldStates() {
|
||||
return DISALLOWED_TLD_STATES_FOR_LAUNCH_FLOWS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_APPLICATION_UPDATE;
|
||||
}
|
||||
|
||||
/** Application status prohibits this domain update. */
|
||||
static class ApplicationStatusProhibitsUpdateException extends StatusProhibitsOperationException {
|
||||
public ApplicationStatusProhibitsUpdateException(ApplicationStatus status) {
|
||||
super(String.format(
|
||||
"Applications in state %s can not be updated",
|
||||
UPPER_UNDERSCORE.to(LOWER_CAMEL, status.name())));
|
||||
}
|
||||
}
|
||||
}
|
128
java/google/registry/flows/domain/DomainCheckFlow.java
Normal file
128
java/google/registry/flows/domain/DomainCheckFlow.java
Normal file
|
@ -0,0 +1,128 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.getReservationType;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
|
||||
import static com.google.domain.registry.model.EppResourceUtils.checkResourcesExist;
|
||||
import static com.google.domain.registry.model.registry.label.ReservationType.UNRESERVED;
|
||||
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
|
||||
import static com.google.domain.registry.util.DomainNameUtils.getTldFromDomainName;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import com.google.domain.registry.model.domain.fee.FeeCheckExtension;
|
||||
import com.google.domain.registry.model.domain.fee.FeeCheckResponseExtension;
|
||||
import com.google.domain.registry.model.domain.fee.FeeCheckResponseExtension.FeeCheck;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCheckExtension;
|
||||
import com.google.domain.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData.DomainCheck;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData.DomainCheckData;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.registry.label.ReservationType;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that checks whether a domain can be provisioned.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCheckFlow.TooManyResourceChecksException}
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link DomainFlowUtils.BadDomainNameCharacterException}
|
||||
* @error {@link DomainFlowUtils.BadDomainNamePartsCountException}
|
||||
* @error {@link DomainFlowUtils.BadPeriodUnitException}
|
||||
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
|
||||
* @error {@link DomainFlowUtils.DashesInThirdAndFourthException}
|
||||
* @error {@link DomainFlowUtils.DomainLabelTooLongException}
|
||||
* @error {@link DomainFlowUtils.EmptyDomainNamePartException}
|
||||
* @error {@link DomainFlowUtils.FeeChecksDontSupportPhasesException}
|
||||
* @error {@link DomainFlowUtils.InvalidIdnDomainLabelException}
|
||||
* @error {@link DomainFlowUtils.InvalidPunycodeException}
|
||||
* @error {@link DomainFlowUtils.LeadingDashException}
|
||||
* @error {@link DomainFlowUtils.RestoresAreAlwaysForOneYearException}
|
||||
* @error {@link DomainFlowUtils.TldDoesNotExistException}
|
||||
* @error {@link DomainFlowUtils.TrailingDashException}
|
||||
* @error {@link DomainFlowUtils.UnknownFeeCommandException}
|
||||
* @error {@link DomainCheckFlow.OnlyCheckedNamesCanBeFeeCheckedException}
|
||||
*/
|
||||
public class DomainCheckFlow extends BaseDomainCheckFlow {
|
||||
|
||||
@Override
|
||||
protected void initDomainCheckFlow() throws EppException {
|
||||
registerExtensions(LaunchCheckExtension.class, FeeCheckExtension.class);
|
||||
}
|
||||
|
||||
private String getMessageForCheck(String targetId, Set<String> existingIds) {
|
||||
if (existingIds.contains(targetId)) {
|
||||
return "In use";
|
||||
}
|
||||
InternetDomainName domainName = domainNames.get(targetId);
|
||||
ReservationType reservationType = getReservationType(domainName);
|
||||
Registry registry = Registry.get(domainName.parent().toString());
|
||||
if (reservationType == UNRESERVED
|
||||
&& registry.isPremiumName(domainName)
|
||||
&& registry.getPremiumPriceAckRequired()
|
||||
&& !nullToEmpty(sessionMetadata.getServiceExtensionUris()).contains(
|
||||
ServiceExtension.FEE_0_6.getUri())) {
|
||||
return "Premium names require EPP ext.";
|
||||
}
|
||||
return reservationType.getMessageForCheck();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CheckData getCheckData() {
|
||||
Set<String> existingIds = checkResourcesExist(resourceClass, targetIds, now);
|
||||
ImmutableList.Builder<DomainCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String id : targetIds) {
|
||||
String message = getMessageForCheck(id, existingIds);
|
||||
checks.add(DomainCheck.create(message == null, id, message));
|
||||
}
|
||||
return DomainCheckData.create(checks.build());
|
||||
}
|
||||
|
||||
/** Handle the fee check extension. */
|
||||
@Override
|
||||
protected ImmutableList<? extends ResponseExtension> getResponseExtensions() throws EppException {
|
||||
FeeCheckExtension feeCheck = eppInput.getSingleExtension(FeeCheckExtension.class);
|
||||
if (feeCheck == null) {
|
||||
return null; // No fee checks were requested.
|
||||
}
|
||||
ImmutableList.Builder<FeeCheck> feeChecksBuilder = new ImmutableList.Builder<>();
|
||||
for (FeeCheckExtension.DomainCheck domainCheck : feeCheck.getDomains()) {
|
||||
String domainName = domainCheck.getName();
|
||||
if (!domainNames.containsKey(domainName)) {
|
||||
// Although the fee extension explicitly says it's ok to fee check a domain name that you
|
||||
// aren't also availability checking, we forbid it. This makes the experience simpler and
|
||||
// also means we can assume any domain names in the fee checks have been validated.
|
||||
throw new OnlyCheckedNamesCanBeFeeCheckedException();
|
||||
}
|
||||
FeeCheck.Builder builder = new FeeCheck.Builder();
|
||||
handleFeeRequest(domainCheck, builder, domainName, getTldFromDomainName(domainName), now);
|
||||
feeChecksBuilder.add(builder.setName(domainName).build());
|
||||
}
|
||||
return ImmutableList.of(FeeCheckResponseExtension.create(feeChecksBuilder.build()));
|
||||
}
|
||||
|
||||
/** By server policy, fee check names must be listed in the availability check. */
|
||||
static class OnlyCheckedNamesCanBeFeeCheckedException extends ParameterValuePolicyErrorException {
|
||||
OnlyCheckedNamesCanBeFeeCheckedException() {
|
||||
super("By server policy, fee check names must be listed in the availability check");
|
||||
}
|
||||
}
|
||||
}
|
199
java/google/registry/flows/domain/DomainCreateFlow.java
Normal file
199
java/google/registry/flows/domain/DomainCreateFlow.java
Normal file
|
@ -0,0 +1,199 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||
import static com.google.domain.registry.model.index.DomainApplicationIndex.loadActiveApplicationsByDomainName;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.CommandUseErrorException;
|
||||
import com.google.domain.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import com.google.domain.registry.model.billing.BillingEvent;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Reason;
|
||||
import com.google.domain.registry.model.domain.DomainApplication;
|
||||
import com.google.domain.registry.model.domain.DomainResource.Builder;
|
||||
import com.google.domain.registry.model.domain.GracePeriod;
|
||||
import com.google.domain.registry.model.domain.fee.FeeCreateExtension;
|
||||
import com.google.domain.registry.model.domain.launch.LaunchCreateExtension;
|
||||
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.registry.Registry.TldState;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.tmch.LordnTask;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that creates a new domain resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.EppException.UnimplementedExtensionException}
|
||||
* @error {@link com.google.domain.registry.flows.LoggedInFlow.UndeclaredServiceExtensionException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCreateOrMutateFlow.OnlyToolCanPassMetadataException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlow.BadCommandForRegistryPhaseException}
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link BaseDomainCreateFlow.AcceptedTooLongAgoException}
|
||||
* @error {@link BaseDomainCreateFlow.ClaimsPeriodEndedException}
|
||||
* @error {@link BaseDomainCreateFlow.ExpiredClaimException}
|
||||
* @error {@link BaseDomainCreateFlow.InvalidTcnIdChecksumException}
|
||||
* @error {@link BaseDomainCreateFlow.InvalidTrademarkValidatorException}
|
||||
* @error {@link BaseDomainCreateFlow.MalformedTcnIdException}
|
||||
* @error {@link BaseDomainCreateFlow.MaxSigLifeNotSupportedException}
|
||||
* @error {@link BaseDomainCreateFlow.MissingClaimsNoticeException}
|
||||
* @error {@link BaseDomainCreateFlow.UnexpectedClaimsNoticeException}
|
||||
* @error {@link BaseDomainCreateFlow.UnsupportedMarkTypeException}
|
||||
* @error {@link DomainCreateFlow.SignedMarksNotAcceptedInCurrentPhaseException}
|
||||
* @error {@link DomainFlowUtils.BadDomainNameCharacterException}
|
||||
* @error {@link DomainFlowUtils.BadDomainNamePartsCountException}
|
||||
* @error {@link DomainFlowUtils.BadPeriodUnitException}
|
||||
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
|
||||
* @error {@link DomainFlowUtils.CurrencyValueScaleException}
|
||||
* @error {@link DomainFlowUtils.DashesInThirdAndFourthException}
|
||||
* @error {@link DomainFlowUtils.DomainLabelTooLongException}
|
||||
* @error {@link DomainFlowUtils.DomainReservedException}
|
||||
* @error {@link DomainFlowUtils.DuplicateContactForRoleException}
|
||||
* @error {@link DomainFlowUtils.EmptyDomainNamePartException}
|
||||
* @error {@link DomainFlowUtils.FeesMismatchException}
|
||||
* @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException}
|
||||
* @error {@link DomainFlowUtils.InvalidIdnDomainLabelException}
|
||||
* @error {@link DomainFlowUtils.InvalidPunycodeException}
|
||||
* @error {@link DomainFlowUtils.LeadingDashException}
|
||||
* @error {@link DomainFlowUtils.LinkedResourceDoesNotExistException}
|
||||
* @error {@link DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException}
|
||||
* @error {@link DomainFlowUtils.MissingAdminContactException}
|
||||
* @error {@link DomainFlowUtils.MissingContactTypeException}
|
||||
* @error {@link DomainFlowUtils.MissingRegistrantException}
|
||||
* @error {@link DomainFlowUtils.MissingTechnicalContactException}
|
||||
* @error {@link DomainFlowUtils.NameserverNotAllowedException}
|
||||
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
|
||||
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
|
||||
* @error {@link DomainFlowUtils.TldDoesNotExistException}
|
||||
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
|
||||
* @error {@link DomainFlowUtils.TooManyNameserversException}
|
||||
* @error {@link DomainFlowUtils.TrailingDashException}
|
||||
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
|
||||
* @error {@link DomainCreateFlow.DomainHasOpenApplicationsException}
|
||||
* @error {@link DomainCreateFlow.NoGeneralRegistrationsInCurrentPhaseException}
|
||||
*/
|
||||
|
||||
public class DomainCreateFlow extends DomainCreateOrAllocateFlow {
|
||||
|
||||
private static final Set<TldState> QLP_SMD_ALLOWED_STATES =
|
||||
Sets.immutableEnumSet(TldState.SUNRISE, TldState.SUNRUSH);
|
||||
|
||||
private boolean isAnchorTenant() {
|
||||
return isAnchorTenantViaReservation || isAnchorTenantViaExtension;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyDomainCreateIsAllowed() throws EppException {
|
||||
String tld = getTld();
|
||||
validateFeeChallenge(targetId, tld, feeCreate, createCost);
|
||||
if (!superuser) {
|
||||
// Prohibit creating a domain if there is an open application for the same name.
|
||||
for (DomainApplication application : loadActiveApplicationsByDomainName(targetId, now)) {
|
||||
if (!application.getApplicationStatus().isFinalStatus()) {
|
||||
throw new DomainHasOpenApplicationsException();
|
||||
}
|
||||
}
|
||||
// Prohibit registrations for non-qlp and non-superuser outside of GA.
|
||||
if (!isAnchorTenant()
|
||||
&& Registry.get(tld).getTldState(now) != TldState.GENERAL_AVAILABILITY) {
|
||||
throw new NoGeneralRegistrationsInCurrentPhaseException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void initDomainCreateOrAllocateFlow() {
|
||||
registerExtensions(FeeCreateExtension.class, LaunchCreateExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void validateDomainLaunchCreateExtension() throws EppException {
|
||||
// We can assume launchCreate is not null here.
|
||||
// Only QLP domains can have a signed mark on a domain create, and only in sunrise or sunrush.
|
||||
if (hasSignedMarks) {
|
||||
if (isAnchorTenant() && QLP_SMD_ALLOWED_STATES.contains(
|
||||
Registry.get(getTld()).getTldState(now))) {
|
||||
return;
|
||||
}
|
||||
throw new SignedMarksNotAcceptedInCurrentPhaseException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void setDomainCreateOrAllocateProperties(Builder builder) throws EppException {
|
||||
Registry registry = Registry.get(getTld());
|
||||
// Bill for the create.
|
||||
BillingEvent.OneTime createEvent = new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.CREATE)
|
||||
.setTargetId(targetId)
|
||||
.setClientId(getClientId())
|
||||
.setPeriodYears(command.getPeriod().getValue())
|
||||
.setCost(checkNotNull(createCost))
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now.plus(isAnchorTenant()
|
||||
? registry.getAnchorTenantAddGracePeriodLength()
|
||||
: registry.getAddGracePeriodLength()))
|
||||
.setFlags(isAnchorTenant() ? ImmutableSet.of(BillingEvent.Flag.ANCHOR_TENANT) : null)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
ofy().save().entity(createEvent);
|
||||
builder.addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.ADD, createEvent));
|
||||
if (launchCreate != null && (launchCreate.getNotice() != null || hasSignedMarks)) {
|
||||
builder
|
||||
.setLaunchNotice(launchCreate.getNotice())
|
||||
.setSmdId(signedMark == null ? null : signedMark.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enqueueLordnTaskIfNeeded() {
|
||||
if (launchCreate != null && (launchCreate.getNotice() != null || hasSignedMarks)) {
|
||||
LordnTask.enqueueDomainResourceTask(newResource);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_CREATE;
|
||||
}
|
||||
|
||||
/** There is an open application for this domain. */
|
||||
static class DomainHasOpenApplicationsException extends StatusProhibitsOperationException {
|
||||
public DomainHasOpenApplicationsException() {
|
||||
super("There is an open application for this domain");
|
||||
}
|
||||
}
|
||||
|
||||
/** Signed marks are not accepted in the current registry phase. */
|
||||
static class SignedMarksNotAcceptedInCurrentPhaseException extends CommandUseErrorException {
|
||||
public SignedMarksNotAcceptedInCurrentPhaseException() {
|
||||
super("Signed marks are not accepted in the current registry phase");
|
||||
}
|
||||
}
|
||||
|
||||
/** The current registry phase does not allow for general registrations. */
|
||||
static class NoGeneralRegistrationsInCurrentPhaseException extends CommandUseErrorException {
|
||||
public NoGeneralRegistrationsInCurrentPhaseException() {
|
||||
super("The current registry phase does not allow for general registrations");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.flows.domain;
|
||||
|
||||
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.leapSafeAddYears;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.dns.DnsQueue;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.model.billing.BillingEvent;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Flag;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Reason;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.DomainResource.Builder;
|
||||
import com.google.domain.registry.model.domain.Period;
|
||||
import com.google.domain.registry.model.domain.fee.Fee;
|
||||
import com.google.domain.registry.model.domain.fee.FeeCreateResponseExtension;
|
||||
import com.google.domain.registry.model.domain.metadata.MetadataExtension;
|
||||
import com.google.domain.registry.model.eppoutput.CreateData.DomainCreateData;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Result;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** An EPP flow that creates or allocates a new domain resource. */
|
||||
public abstract class DomainCreateOrAllocateFlow
|
||||
extends BaseDomainCreateFlow<DomainResource, Builder> {
|
||||
|
||||
protected boolean isAnchorTenantViaExtension;
|
||||
|
||||
@Override
|
||||
protected final void initDomainCreateFlow() {
|
||||
registerExtensions(MetadataExtension.class);
|
||||
isAnchorTenantViaExtension =
|
||||
(metadataExtension != null && metadataExtension.getIsAnchorTenant());
|
||||
initDomainCreateOrAllocateFlow();
|
||||
}
|
||||
|
||||
protected abstract void initDomainCreateOrAllocateFlow();
|
||||
|
||||
@Override
|
||||
protected final void setDomainCreateProperties(Builder builder) throws EppException {
|
||||
DateTime registrationExpirationTime = leapSafeAddYears(now, command.getPeriod().getValue());
|
||||
// Create a new autorenew billing event and poll message starting at the expiration time.
|
||||
BillingEvent.Recurring autorenewEvent = new BillingEvent.Recurring.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
.setTargetId(targetId)
|
||||
.setClientId(getClientId())
|
||||
.setEventTime(registrationExpirationTime)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
PollMessage.Autorenew autorenewPollMessage = new PollMessage.Autorenew.Builder()
|
||||
.setTargetId(targetId)
|
||||
.setClientId(getClientId())
|
||||
.setEventTime(registrationExpirationTime)
|
||||
.setMsg("Domain was auto-renewed.")
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
ofy().save().<Object>entities(autorenewEvent, autorenewPollMessage);
|
||||
|
||||
builder
|
||||
.setRegistrationExpirationTime(registrationExpirationTime)
|
||||
.setAutorenewBillingEvent(Ref.create(autorenewEvent))
|
||||
.setAutorenewPollMessage(Ref.create(autorenewPollMessage));
|
||||
setDomainCreateOrAllocateProperties(builder);
|
||||
}
|
||||
|
||||
/** Subclasses must override this to set more fields, like any grace period. */
|
||||
protected abstract void setDomainCreateOrAllocateProperties(Builder builder) throws EppException;
|
||||
|
||||
@Override
|
||||
protected final void enqueueTasks() {
|
||||
if (newResource.shouldPublishToDns()) {
|
||||
DnsQueue.create().addDomainRefreshTask(newResource.getFullyQualifiedDomainName());
|
||||
}
|
||||
enqueueLordnTaskIfNeeded();
|
||||
}
|
||||
|
||||
/** Subclasses must override this to enqueue any additional tasks. */
|
||||
protected abstract void enqueueLordnTaskIfNeeded();
|
||||
|
||||
@Override
|
||||
protected final Period getCommandPeriod() {
|
||||
return command.getPeriod();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final EppOutput getOutput() {
|
||||
return createOutput(
|
||||
Result.Code.Success,
|
||||
DomainCreateData.create(
|
||||
newResource.getFullyQualifiedDomainName(),
|
||||
now,
|
||||
newResource.getRegistrationExpirationTime()),
|
||||
(feeCreate == null) ? null : ImmutableList.of(
|
||||
new FeeCreateResponseExtension.Builder()
|
||||
.setCurrency(createCost.getCurrencyUnit())
|
||||
.setFee(ImmutableList.of(Fee.create(createCost.getAmount(), "create")))
|
||||
.build()));
|
||||
}
|
||||
}
|
197
java/google/registry/flows/domain/DomainDeleteFlow.java
Normal file
197
java/google/registry/flows/domain/DomainDeleteFlow.java
Normal file
|
@ -0,0 +1,197 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.SuccessWithActionPending;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.dns.DnsQueue;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.AssociationProhibitsOperationException;
|
||||
import com.google.domain.registry.flows.ResourceSyncDeleteFlow;
|
||||
import com.google.domain.registry.model.billing.BillingEvent;
|
||||
import com.google.domain.registry.model.common.TimeOfYear;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Delete;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.DomainResource.Builder;
|
||||
import com.google.domain.registry.model.domain.GracePeriod;
|
||||
import com.google.domain.registry.model.domain.fee.Credit;
|
||||
import com.google.domain.registry.model.domain.fee.FeeDeleteResponseExtension;
|
||||
import com.google.domain.registry.model.domain.metadata.MetadataExtension;
|
||||
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import com.google.domain.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import com.google.domain.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
import com.google.domain.registry.model.eppoutput.Result.Code;
|
||||
import com.google.domain.registry.model.poll.PendingActionNotificationResponse.DomainPendingActionNotificationResponse;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
|
||||
import org.joda.money.CurrencyUnit;
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that deletes a domain resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCreateOrMutateFlow.OnlyToolCanPassMetadataException}
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link DomainDeleteFlow.DomainToDeleteHasHostsException}
|
||||
*/
|
||||
public class DomainDeleteFlow extends ResourceSyncDeleteFlow<DomainResource, Builder, Delete> {
|
||||
|
||||
PollMessage.OneTime deletePollMessage;
|
||||
|
||||
CurrencyUnit creditsCurrencyUnit;
|
||||
|
||||
ImmutableList<Credit> credits;
|
||||
|
||||
@Override
|
||||
protected void initResourceCreateOrMutateFlow() throws EppException {
|
||||
registerExtensions(SecDnsUpdateExtension.class, MetadataExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyMutationOnOwnedResourceAllowed() throws EppException {
|
||||
checkRegistryStateForTld(existingResource.getTld());
|
||||
checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld());
|
||||
if (!existingResource.getSubordinateHosts().isEmpty()) {
|
||||
throw new DomainToDeleteHasHostsException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void setDeleteProperties(Builder builder) {
|
||||
// Only set to PENDING_DELETE if this domain is not in the Add Grace Period. If domain is in Add
|
||||
// Grace Period, we delete it immediately.
|
||||
// The base class code already handles the immediate delete case, so we only have to handle the
|
||||
// pending delete case here.
|
||||
if (!existingResource.getGracePeriodStatuses().contains(GracePeriodStatus.ADD)) {
|
||||
Registry registry = Registry.get(existingResource.getTld());
|
||||
// By default, this should be 30 days of grace, and 5 days of pending delete. */
|
||||
DateTime deletionTime = now
|
||||
.plus(registry.getRedemptionGracePeriodLength())
|
||||
.plus(registry.getPendingDeleteLength());
|
||||
deletePollMessage = new PollMessage.OneTime.Builder()
|
||||
.setClientId(existingResource.getCurrentSponsorClientId())
|
||||
.setEventTime(deletionTime)
|
||||
.setMsg("Domain deleted.")
|
||||
.setResponseData(ImmutableList.of(DomainPendingActionNotificationResponse.create(
|
||||
existingResource.getFullyQualifiedDomainName(), true, trid, deletionTime)))
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
builder.setStatusValues(ImmutableSet.of(StatusValue.PENDING_DELETE))
|
||||
.setDeletionTime(deletionTime)
|
||||
// Clear out all old grace periods and add REDEMPTION, which does not include a ref
|
||||
// to a billing event because there isn't one for a domain delete.
|
||||
.setGracePeriods(ImmutableSet.of(GracePeriod.createWithoutBillingEvent(
|
||||
GracePeriodStatus.REDEMPTION,
|
||||
now.plus(registry.getRedemptionGracePeriodLength()),
|
||||
getClientId())))
|
||||
.setDeletePollMessage(Key.create(deletePollMessage));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void enqueueTasks() {
|
||||
DnsQueue.create().addDomainRefreshTask(existingResource.getFullyQualifiedDomainName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void modifySyncDeleteRelatedResources() {
|
||||
// Cancel any grace periods that were still active.
|
||||
ImmutableList.Builder<Credit> creditsBuilder = new ImmutableList.Builder<>();
|
||||
for (GracePeriod gracePeriod : existingResource.getGracePeriods()) {
|
||||
// No cancellation is written if the grace period was not for a billable event.
|
||||
if (gracePeriod.hasBillingEvent()) {
|
||||
ofy().save().entity(
|
||||
BillingEvent.Cancellation.forGracePeriod(gracePeriod, historyEntry, targetId));
|
||||
|
||||
Money cost;
|
||||
if (gracePeriod.getType() == GracePeriodStatus.AUTO_RENEW) {
|
||||
TimeOfYear recurrenceTimeOfYear =
|
||||
checkNotNull(gracePeriod.getRecurringBillingEvent()).get().getRecurrenceTimeOfYear();
|
||||
DateTime autoRenewTime = recurrenceTimeOfYear.beforeOrAt(now);
|
||||
cost = Registry.get(existingResource.getTld())
|
||||
.getDomainRenewCost(targetId, 1, autoRenewTime);
|
||||
} else {
|
||||
cost = checkNotNull(gracePeriod.getOneTimeBillingEvent()).get().getCost();
|
||||
}
|
||||
creditsBuilder.add(Credit.create(
|
||||
cost.negated().getAmount(),
|
||||
String.format("%s credit", gracePeriod.getType().getXmlName())));
|
||||
creditsCurrencyUnit = cost.getCurrencyUnit();
|
||||
}
|
||||
}
|
||||
credits = creditsBuilder.build();
|
||||
|
||||
// If the delete isn't immediate, save the poll message for when the delete will happen.
|
||||
if (deletePollMessage != null) {
|
||||
ofy().save().entity(deletePollMessage);
|
||||
}
|
||||
// Close the autorenew billing event and poll message. This may delete the poll message.
|
||||
updateAutorenewRecurrenceEndTime(existingResource, now);
|
||||
|
||||
// If there's a pending transfer, the gaining client's autorenew billing
|
||||
// event and poll message will already have been deleted in
|
||||
// ResourceDeleteFlow since it's listed in serverApproveEntities.
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Code getDeleteResultCode() {
|
||||
return newResource.getDeletionTime().isAfter(now)
|
||||
? SuccessWithActionPending : Success;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ImmutableList<? extends ResponseExtension> getDeleteResponseExtensions() {
|
||||
if (!credits.isEmpty()
|
||||
&& nullToEmpty(sessionMetadata.getServiceExtensionUris()).contains(
|
||||
ServiceExtension.FEE_0_6.getUri())) {
|
||||
return ImmutableList.of(new FeeDeleteResponseExtension.Builder()
|
||||
.setCurrency(checkNotNull(creditsCurrencyUnit))
|
||||
.setCredits(credits)
|
||||
.build());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_DELETE;
|
||||
}
|
||||
|
||||
/** Domain to be deleted has subordinate hosts. */
|
||||
static class DomainToDeleteHasHostsException extends AssociationProhibitsOperationException {
|
||||
public DomainToDeleteHasHostsException() {
|
||||
super("Domain to be deleted has subordinate hosts");
|
||||
}
|
||||
}
|
||||
}
|
1006
java/google/registry/flows/domain/DomainFlowUtils.java
Normal file
1006
java/google/registry/flows/domain/DomainFlowUtils.java
Normal file
File diff suppressed because it is too large
Load diff
94
java/google/registry/flows/domain/DomainInfoFlow.java
Normal file
94
java/google/registry/flows/domain/DomainInfoFlow.java
Normal file
|
@ -0,0 +1,94 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.handleFeeRequest;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.DomainResource.Builder;
|
||||
import com.google.domain.registry.model.domain.fee.FeeInfoExtension;
|
||||
import com.google.domain.registry.model.domain.fee.FeeInfoResponseExtension;
|
||||
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import com.google.domain.registry.model.domain.rgp.RgpInfoExtension;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
|
||||
/**
|
||||
* An EPP flow that reads a domain.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException}
|
||||
* @error {@link DomainFlowUtils.BadPeriodUnitException}
|
||||
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
|
||||
* @error {@link DomainFlowUtils.FeeChecksDontSupportPhasesException}
|
||||
* @error {@link DomainFlowUtils.RestoresAreAlwaysForOneYearException}
|
||||
*/
|
||||
public class DomainInfoFlow extends BaseDomainInfoFlow<DomainResource, Builder> {
|
||||
|
||||
@Override
|
||||
protected void initSingleResourceFlow() throws EppException {
|
||||
registerExtensions(FeeInfoExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final DomainResource getResourceInfo() {
|
||||
// If authInfo is non-null, then the caller is authorized to see the full information since we
|
||||
// will have already verified the authInfo is valid in ResourceQueryFlow.verifyIsAllowed().
|
||||
if (!getClientId().equals(existingResource.getCurrentSponsorClientId())
|
||||
&& command.getAuthInfo() == null) {
|
||||
// Registrars can only see a few fields on unauthorized domains.
|
||||
// This is a policy decision that is left up to us by the rfcs.
|
||||
return new DomainResource.Builder()
|
||||
.setFullyQualifiedDomainName(existingResource.getFullyQualifiedDomainName())
|
||||
.setRepoId(existingResource.getRepoId())
|
||||
.setCurrentSponsorClientId(existingResource.getCurrentSponsorClientId())
|
||||
// If we didn't do this, we'd get implicit status values.
|
||||
.buildWithoutImplicitStatusValues();
|
||||
}
|
||||
Builder info = existingResource.asBuilder();
|
||||
if (!command.getHostsRequest().requestSubordinate()) {
|
||||
info.setSubordinateHosts(null);
|
||||
}
|
||||
if (!command.getHostsRequest().requestDelegated()) {
|
||||
// Delegated hosts are present by default, so clear them out if they aren't wanted.
|
||||
// This requires overriding the implicit status values so that we don't get INACTIVE added due
|
||||
// to the missing nameservers.
|
||||
return info.setNameservers(null).buildWithoutImplicitStatusValues();
|
||||
}
|
||||
return info.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ImmutableList<ResponseExtension> getDomainResponseExtensions()
|
||||
throws EppException {
|
||||
ImmutableList.Builder<ResponseExtension> extensions = new ImmutableList.Builder<>();
|
||||
// According to RFC 5910 section 2, we should only return this if the client specified the
|
||||
// "urn:ietf:params:xml:ns:rgp-1.0" when logging in. However, this is a "SHOULD" not a "MUST"
|
||||
// and we are going to ignore it; clients who don't care about rgp can just ignore it.
|
||||
ImmutableSet<GracePeriodStatus> gracePeriodStatuses = existingResource.getGracePeriodStatuses();
|
||||
if (!gracePeriodStatuses.isEmpty()) {
|
||||
extensions.add(RgpInfoExtension.create(gracePeriodStatuses));
|
||||
}
|
||||
FeeInfoExtension feeInfo = eppInput.getSingleExtension(FeeInfoExtension.class);
|
||||
if (feeInfo != null) { // Fee check was requested.
|
||||
FeeInfoResponseExtension.Builder builder = new FeeInfoResponseExtension.Builder();
|
||||
handleFeeRequest(feeInfo, builder, getTargetId(), existingResource.getTld(), now);
|
||||
extensions.add(builder.build());
|
||||
}
|
||||
return extensions.build();
|
||||
}
|
||||
}
|
206
java/google/registry/flows/domain/DomainRenewFlow.java
Normal file
206
java/google/registry/flows/domain/DomainRenewFlow.java
Normal file
|
@ -0,0 +1,206 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.newAutorenewBillingEvent;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.newAutorenewPollMessage;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears;
|
||||
import static com.google.domain.registry.model.domain.DomainResource.MAX_REGISTRATION_YEARS;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.leapSafeAddYears;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.ObjectPendingTransferException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||
import com.google.domain.registry.flows.OwnedResourceMutateFlow;
|
||||
import com.google.domain.registry.model.billing.BillingEvent;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Reason;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Renew;
|
||||
import com.google.domain.registry.model.domain.DomainRenewData;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.GracePeriod;
|
||||
import com.google.domain.registry.model.domain.Period;
|
||||
import com.google.domain.registry.model.domain.fee.Fee;
|
||||
import com.google.domain.registry.model.domain.fee.FeeRenewExtension;
|
||||
import com.google.domain.registry.model.domain.fee.FeeRenewResponseExtension;
|
||||
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.model.transfer.TransferStatus;
|
||||
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that updates a domain resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link DomainFlowUtils.BadPeriodUnitException}
|
||||
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
|
||||
* @error {@link DomainFlowUtils.CurrencyValueScaleException}
|
||||
* @error {@link DomainFlowUtils.FeesMismatchException}
|
||||
* @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException}
|
||||
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
|
||||
* @error {@link DomainRenewFlow.DomainHasPendingTransferException}
|
||||
* @error {@link DomainRenewFlow.ExceedsMaxRegistrationYearsException}
|
||||
* @error {@link DomainRenewFlow.IncorrectCurrentExpirationDateException}
|
||||
*/
|
||||
public class DomainRenewFlow extends OwnedResourceMutateFlow<DomainResource, Renew> {
|
||||
|
||||
private static final Set<StatusValue> RENEW_DISALLOWED_STATUSES = ImmutableSet.of(
|
||||
StatusValue.CLIENT_RENEW_PROHIBITED,
|
||||
StatusValue.PENDING_DELETE,
|
||||
StatusValue.SERVER_RENEW_PROHIBITED);
|
||||
|
||||
protected FeeRenewExtension feeRenew;
|
||||
protected Money renewCost;
|
||||
|
||||
@Override
|
||||
protected Set<StatusValue> getDisallowedStatuses() {
|
||||
return RENEW_DISALLOWED_STATUSES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void initResourceCreateOrMutateFlow() throws EppException {
|
||||
registerExtensions(FeeRenewExtension.class);
|
||||
feeRenew = eppInput.getSingleExtension(FeeRenewExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verifyMutationOnOwnedResourceAllowed() throws EppException {
|
||||
checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld());
|
||||
// Verify that the resource does not have a pending transfer on it.
|
||||
if (existingResource.getTransferData().getTransferStatus() == TransferStatus.PENDING) {
|
||||
throw new DomainHasPendingTransferException(targetId);
|
||||
}
|
||||
verifyUnitIsYears(command.getPeriod());
|
||||
// If the date they specify doesn't match the expiration, fail. (This is an idempotence check).
|
||||
if (!command.getCurrentExpirationDate().equals(
|
||||
existingResource.getRegistrationExpirationTime().toLocalDate())) {
|
||||
throw new IncorrectCurrentExpirationDateException();
|
||||
}
|
||||
renewCost = Registry.get(existingResource.getTld())
|
||||
.getDomainRenewCost(targetId, command.getPeriod().getValue(), now);
|
||||
validateFeeChallenge(targetId, existingResource.getTld(), feeRenew, renewCost);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected DomainResource createOrMutateResource() {
|
||||
DateTime newExpirationTime = leapSafeAddYears(
|
||||
existingResource.getRegistrationExpirationTime(), command.getPeriod().getValue());
|
||||
// Bill for this explicit renew itself.
|
||||
BillingEvent.OneTime explicitRenewEvent = new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setTargetId(targetId)
|
||||
.setClientId(getClientId())
|
||||
.setPeriodYears(command.getPeriod().getValue())
|
||||
.setCost(checkNotNull(renewCost))
|
||||
.setEventTime(now)
|
||||
.setBillingTime(
|
||||
now.plus(Registry.get(existingResource.getTld()).getRenewGracePeriodLength()))
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
// End the old autorenew billing event and poll message now. This may delete the poll message.
|
||||
updateAutorenewRecurrenceEndTime(existingResource, now);
|
||||
// Create a new autorenew billing event and poll message starting at the new expiration time.
|
||||
BillingEvent.Recurring newAutorenewEvent = newAutorenewBillingEvent(existingResource)
|
||||
.setEventTime(newExpirationTime)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
PollMessage.Autorenew newAutorenewPollMessage = newAutorenewPollMessage(existingResource)
|
||||
.setEventTime(newExpirationTime)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
ofy().save().<Object>entities(explicitRenewEvent, newAutorenewEvent, newAutorenewPollMessage);
|
||||
return existingResource.asBuilder()
|
||||
.setRegistrationExpirationTime(newExpirationTime)
|
||||
.setAutorenewBillingEvent(Ref.create(newAutorenewEvent))
|
||||
.setAutorenewPollMessage(Ref.create(newAutorenewPollMessage))
|
||||
.addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.RENEW, explicitRenewEvent))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verifyNewStateIsAllowed() throws EppException {
|
||||
if (leapSafeAddYears(now, MAX_REGISTRATION_YEARS)
|
||||
.isBefore(newResource.getRegistrationExpirationTime())) {
|
||||
throw new ExceedsMaxRegistrationYearsException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_RENEW;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Period getCommandPeriod() {
|
||||
return command.getPeriod();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final EppOutput getOutput() {
|
||||
return createOutput(
|
||||
Success,
|
||||
DomainRenewData.create(
|
||||
newResource.getFullyQualifiedDomainName(),
|
||||
newResource.getRegistrationExpirationTime()),
|
||||
(feeRenew == null) ? null : ImmutableList.of(
|
||||
new FeeRenewResponseExtension.Builder()
|
||||
.setCurrency(renewCost.getCurrencyUnit())
|
||||
.setFee(ImmutableList.of(Fee.create(renewCost.getAmount(), "renew")))
|
||||
.build()));
|
||||
}
|
||||
|
||||
/** The domain has a pending transfer on it and so can't be explicitly renewed. */
|
||||
public static class DomainHasPendingTransferException extends ObjectPendingTransferException {
|
||||
public DomainHasPendingTransferException(String targetId) {
|
||||
super(targetId);
|
||||
}
|
||||
}
|
||||
|
||||
/** The current expiration date is incorrect. */
|
||||
static class IncorrectCurrentExpirationDateException extends ParameterValueRangeErrorException {
|
||||
public IncorrectCurrentExpirationDateException() {
|
||||
super("The current expiration date is incorrect");
|
||||
}
|
||||
}
|
||||
|
||||
/** New registration period exceeds maximum number of years. */
|
||||
static class ExceedsMaxRegistrationYearsException extends ParameterValueRangeErrorException {
|
||||
public ExceedsMaxRegistrationYearsException() {
|
||||
super(String.format(
|
||||
"Registrations cannot extend for more than %d years into the future",
|
||||
MAX_REGISTRATION_YEARS));
|
||||
}
|
||||
}
|
||||
}
|
208
java/google/registry/flows/domain/DomainRestoreRequestFlow.java
Normal file
208
java/google/registry/flows/domain/DomainRestoreRequestFlow.java
Normal file
|
@ -0,0 +1,208 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.newAutorenewBillingEvent;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.newAutorenewPollMessage;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyNotReserved;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.domain.registry.dns.DnsQueue;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.CommandUseErrorException;
|
||||
import com.google.domain.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import com.google.domain.registry.flows.OwnedResourceMutateFlow;
|
||||
import com.google.domain.registry.model.billing.BillingEvent;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Reason;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Update;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.fee.Fee;
|
||||
import com.google.domain.registry.model.domain.fee.FeeUpdateExtension;
|
||||
import com.google.domain.registry.model.domain.fee.FeeUpdateResponseExtension;
|
||||
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import com.google.domain.registry.model.domain.rgp.RgpUpdateExtension;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.index.ForeignKeyIndex;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that requests that a deleted domain be restored.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.EppException.UnimplementedExtensionException}
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
|
||||
* @error {@link DomainFlowUtils.CurrencyValueScaleException}
|
||||
* @error {@link DomainFlowUtils.DomainReservedException}
|
||||
* @error {@link DomainFlowUtils.FeesMismatchException}
|
||||
* @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException}
|
||||
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
|
||||
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
|
||||
* @error {@link DomainRestoreRequestFlow.DomainNotEligibleForRestoreException}
|
||||
* @error {@link DomainRestoreRequestFlow.RestoreCommandIncludesChangesException}
|
||||
*/
|
||||
public class DomainRestoreRequestFlow extends OwnedResourceMutateFlow<DomainResource, Update> {
|
||||
|
||||
protected FeeUpdateExtension feeUpdate;
|
||||
protected Money restoreCost;
|
||||
protected Money renewCost;
|
||||
|
||||
@Override
|
||||
protected final void initResourceCreateOrMutateFlow() throws EppException {
|
||||
registerExtensions(FeeUpdateExtension.class, RgpUpdateExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyMutationOnOwnedResourceAllowed() throws EppException {
|
||||
// No other changes can be specified on a restore request.
|
||||
if (!command.noChangesPresent()) {
|
||||
throw new RestoreCommandIncludesChangesException();
|
||||
}
|
||||
|
||||
// Domain must be in pendingDelete and within the redemptionPeriod to be eligible for restore.
|
||||
if (!existingResource.getStatusValues().contains(StatusValue.PENDING_DELETE)
|
||||
|| !existingResource.getGracePeriodStatuses().contains(GracePeriodStatus.REDEMPTION)) {
|
||||
throw new DomainNotEligibleForRestoreException();
|
||||
}
|
||||
|
||||
String tld = existingResource.getTld();
|
||||
checkAllowedAccessToTld(getAllowedTlds(), tld);
|
||||
if (!superuser) {
|
||||
verifyNotReserved(InternetDomainName.from(targetId), false);
|
||||
verifyPremiumNameIsNotBlocked(targetId, tld, getClientId());
|
||||
}
|
||||
feeUpdate = eppInput.getSingleExtension(FeeUpdateExtension.class);
|
||||
restoreCost = Registry.get(tld).getStandardRestoreCost();
|
||||
renewCost = Registry.get(tld).getDomainRenewCost(targetId, 1, now);
|
||||
validateFeeChallenge(targetId, tld, feeUpdate, restoreCost, renewCost);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final DomainResource createOrMutateResource() throws EppException {
|
||||
// We don't preserve the original expiration time of the domain when we restore, since doing so
|
||||
// would require us to know if they received a grace period refund when they deleted the domain,
|
||||
// and to charge them for that again. Instead, we just say that all restores get a fresh year of
|
||||
// registration and bill them for that accordingly.
|
||||
DateTime newExpirationTime = now.plusYears(1);
|
||||
|
||||
// Bill for the restore.
|
||||
BillingEvent.OneTime restoreEvent = new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.RESTORE)
|
||||
.setTargetId(targetId)
|
||||
.setClientId(getClientId())
|
||||
.setCost(restoreCost)
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
// Create a new autorenew billing event and poll message starting at the new expiration time.
|
||||
BillingEvent.Recurring autorenewEvent = newAutorenewBillingEvent(existingResource)
|
||||
.setEventTime(newExpirationTime)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
PollMessage.Autorenew autorenewPollMessage = newAutorenewPollMessage(existingResource)
|
||||
.setEventTime(newExpirationTime)
|
||||
.setAutorenewEndTime(END_OF_TIME)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
|
||||
// Also bill for the 1 year cost of a domain renew. This is to avoid registrants being able to
|
||||
// game the system for premium names by renewing, deleting, and then restoring to get a free
|
||||
// year. Note that this billing event has no grace period; it is effective immediately.
|
||||
BillingEvent.OneTime renewEvent = new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setTargetId(targetId)
|
||||
.setClientId(getClientId())
|
||||
.setPeriodYears(1)
|
||||
.setCost(renewCost)
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
|
||||
ofy().save().<Object>entities(restoreEvent, autorenewEvent, autorenewPollMessage, renewEvent);
|
||||
return existingResource.asBuilder()
|
||||
.setRegistrationExpirationTime(newExpirationTime)
|
||||
.setDeletionTime(END_OF_TIME)
|
||||
.setStatusValues(null)
|
||||
.setGracePeriods(null)
|
||||
.setDeletePollMessage(null)
|
||||
.setAutorenewBillingEvent(Ref.create(autorenewEvent))
|
||||
.setAutorenewPollMessage(Ref.create(autorenewPollMessage))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void modifyRelatedResources() {
|
||||
// Update the relevant {@link ForeignKey} to cache the new deletion time.
|
||||
ofy().save().entity(ForeignKeyIndex.create(newResource, newResource.getDeletionTime()));
|
||||
ofy().delete().key(existingResource.getDeletePollMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enqueueTasks() {
|
||||
DnsQueue.create().addDomainRefreshTask(existingResource.getFullyQualifiedDomainName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_RESTORE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final EppOutput getOutput() {
|
||||
return createOutput(
|
||||
Success,
|
||||
null,
|
||||
(feeUpdate == null) ? null : ImmutableList.of(
|
||||
new FeeUpdateResponseExtension.Builder()
|
||||
.setCurrency(restoreCost.getCurrencyUnit())
|
||||
.setFee(ImmutableList.of(
|
||||
Fee.create(restoreCost.getAmount(), "restore"),
|
||||
Fee.create(renewCost.getAmount(), "renew")))
|
||||
.build()));
|
||||
}
|
||||
|
||||
/** Restore command cannot have other changes specified. */
|
||||
static class RestoreCommandIncludesChangesException extends CommandUseErrorException {
|
||||
public RestoreCommandIncludesChangesException() {
|
||||
super("Restore command cannot have other changes specified");
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain is not eligible for restore. */
|
||||
static class DomainNotEligibleForRestoreException extends StatusProhibitsOperationException {
|
||||
public DomainNotEligibleForRestoreException() {
|
||||
super("Domain is not eligible for restore");
|
||||
}
|
||||
}
|
||||
}
|
140
java/google/registry/flows/domain/DomainTransferApproveFlow.java
Normal file
140
java/google/registry/flows/domain/DomainTransferApproveFlow.java
Normal file
|
@ -0,0 +1,140 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime;
|
||||
import static com.google.domain.registry.model.domain.DomainResource.extendRegistrationWithCap;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.FluentIterable;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.ResourceTransferApproveFlow;
|
||||
import com.google.domain.registry.model.billing.BillingEvent;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Flag;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Reason;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Transfer;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.DomainResource.Builder;
|
||||
import com.google.domain.registry.model.domain.GracePeriod;
|
||||
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.model.transfer.TransferData;
|
||||
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow that approves a pending transfer on a {@link DomainResource}.
|
||||
* <p>
|
||||
* The logic in this flow, which handles client approvals, very closely parallels the logic in
|
||||
* {@link DomainResource#cloneProjectedAtTime} which handles implicit server approvals.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException}
|
||||
*/
|
||||
public class DomainTransferApproveFlow extends
|
||||
ResourceTransferApproveFlow<DomainResource, Builder, Transfer> {
|
||||
|
||||
@Override
|
||||
protected void verifyOwnedResourcePendingTransferMutationAllowed() throws EppException {
|
||||
checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void setTransferApproveProperties(Builder builder) {
|
||||
TransferData transferData = existingResource.getTransferData();
|
||||
String gainingClientId = transferData.getGainingClientId();
|
||||
String tld = existingResource.getTld();
|
||||
int extraYears = transferData.getExtendedRegistrationYears();
|
||||
// Bill for the transfer.
|
||||
BillingEvent.OneTime billingEvent = new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.TRANSFER)
|
||||
.setTargetId(targetId)
|
||||
.setClientId(gainingClientId)
|
||||
.setPeriodYears(extraYears)
|
||||
.setCost(Registry.get(tld).getDomainRenewCost(
|
||||
targetId,
|
||||
extraYears,
|
||||
transferData.getTransferRequestTime()))
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now.plus(Registry.get(tld).getTransferGracePeriodLength()))
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
ofy().save().entity(billingEvent);
|
||||
// If we are within an autorenew grace period, cancel the autorenew billing event and reduce
|
||||
// the number of years to extend the registration by one.
|
||||
GracePeriod autorenewGrace = Iterables.getOnlyElement(FluentIterable
|
||||
.from(existingResource.getGracePeriods())
|
||||
.filter(new Predicate<GracePeriod>(){
|
||||
@Override
|
||||
public boolean apply(GracePeriod gracePeriod) {
|
||||
return GracePeriodStatus.AUTO_RENEW.equals(gracePeriod.getType());
|
||||
}}), null);
|
||||
if (autorenewGrace != null) {
|
||||
extraYears--;
|
||||
ofy().save().entity(
|
||||
BillingEvent.Cancellation.forGracePeriod(autorenewGrace, historyEntry, targetId));
|
||||
}
|
||||
// Close the old autorenew event and poll message at the transfer time (aka now). This may end
|
||||
// up deleting the poll message.
|
||||
updateAutorenewRecurrenceEndTime(existingResource, now);
|
||||
DateTime newExpirationTime = extendRegistrationWithCap(
|
||||
now, existingResource.getRegistrationExpirationTime(), extraYears);
|
||||
// Create a new autorenew event starting at the expiration time.
|
||||
BillingEvent.Recurring autorenewEvent = new BillingEvent.Recurring.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
.setTargetId(targetId)
|
||||
.setClientId(gainingClientId)
|
||||
.setEventTime(newExpirationTime)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
ofy().save().entity(autorenewEvent);
|
||||
// Create a new autorenew poll message.
|
||||
PollMessage.Autorenew gainingClientAutorenewPollMessage = new PollMessage.Autorenew.Builder()
|
||||
.setTargetId(targetId)
|
||||
.setClientId(gainingClientId)
|
||||
.setEventTime(newExpirationTime)
|
||||
.setAutorenewEndTime(END_OF_TIME)
|
||||
.setMsg("Domain was auto-renewed.")
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
ofy().save().entity(gainingClientAutorenewPollMessage);
|
||||
builder
|
||||
.setRegistrationExpirationTime(newExpirationTime)
|
||||
.setAutorenewBillingEvent(Ref.create(autorenewEvent))
|
||||
.setAutorenewPollMessage(Ref.create(gainingClientAutorenewPollMessage))
|
||||
// Remove all the old grace periods and add a new one for the transfer.
|
||||
.setGracePeriods(ImmutableSet.of(
|
||||
GracePeriod.forBillingEvent(GracePeriodStatus.TRANSFER, billingEvent)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_TRANSFER_APPROVE;
|
||||
}
|
||||
}
|
|
@ -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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.ResourceTransferCancelFlow;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Transfer;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.DomainResource.Builder;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
/**
|
||||
* An EPP flow that cancels a pending transfer on a {@link DomainResource}.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferCancelFlow.NotTransferInitiatorException}
|
||||
*/
|
||||
public class DomainTransferCancelFlow
|
||||
extends ResourceTransferCancelFlow<DomainResource, Builder, Transfer> {
|
||||
|
||||
/**
|
||||
* Reopen the autorenew event and poll message that we closed for the implicit transfer.
|
||||
* This may end up recreating the autorenew poll message if it was deleted when the transfer
|
||||
* request was made.
|
||||
*/
|
||||
@Override
|
||||
protected final void modifyRelatedResourcesForTransferCancel() {
|
||||
updateAutorenewRecurrenceEndTime(existingResource, END_OF_TIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verifyTransferCancelMutationAllowed() throws EppException {
|
||||
checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_TRANSFER_CANCEL;
|
||||
}
|
||||
}
|
|
@ -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.flows.domain;
|
||||
|
||||
import com.google.domain.registry.flows.ResourceTransferQueryFlow;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Transfer;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
|
||||
/**
|
||||
* An EPP flow that queries a pending transfer on a {@link DomainResource}.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferQueryFlow.NoTransferHistoryToQueryException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferQueryFlow.NotAuthorizedToViewTransferException}
|
||||
*/
|
||||
public class DomainTransferQueryFlow extends ResourceTransferQueryFlow<DomainResource, Transfer> {}
|
|
@ -0,0 +1,58 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.ResourceTransferRejectFlow;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Transfer;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.DomainResource.Builder;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
/**
|
||||
* An EPP flow that rejects a pending transfer on a {@link DomainResource}.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutatePendingTransferFlow.NotPendingTransferException}
|
||||
*/
|
||||
public class DomainTransferRejectFlow
|
||||
extends ResourceTransferRejectFlow<DomainResource, Builder, Transfer> {
|
||||
|
||||
@Override
|
||||
protected void verifyOwnedResourcePendingTransferMutationAllowed() throws EppException {
|
||||
checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld());
|
||||
}
|
||||
|
||||
/**
|
||||
* Reopen the autorenew event and poll message that we closed for the implicit transfer.
|
||||
* This may end up recreating the poll message if it was deleted upon the transfer request.
|
||||
*/
|
||||
@Override
|
||||
protected final void modifyRelatedResourcesForTransferReject() {
|
||||
updateAutorenewRecurrenceEndTime(existingResource, END_OF_TIME);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_TRANSFER_REJECT;
|
||||
}
|
||||
}
|
242
java/google/registry/flows/domain/DomainTransferRequestFlow.java
Normal file
242
java/google/registry/flows/domain/DomainTransferRequestFlow.java
Normal file
|
@ -0,0 +1,242 @@
|
|||
// 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.flows.domain;
|
||||
|
||||
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.checkAllowedAccessToTld;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.updateAutorenewRecurrenceEndTime;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.validateFeeChallenge;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyPremiumNameIsNotBlocked;
|
||||
import static com.google.domain.registry.flows.domain.DomainFlowUtils.verifyUnitIsYears;
|
||||
import static com.google.domain.registry.model.domain.DomainResource.extendRegistrationWithCap;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.END_OF_TIME;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.ResourceTransferRequestFlow;
|
||||
import com.google.domain.registry.model.billing.BillingEvent;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Flag;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Reason;
|
||||
import com.google.domain.registry.model.domain.DomainCommand.Transfer;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.Period;
|
||||
import com.google.domain.registry.model.domain.fee.Fee;
|
||||
import com.google.domain.registry.model.domain.fee.FeeTransferExtension;
|
||||
import com.google.domain.registry.model.domain.fee.FeeTransferResponseExtension;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
import com.google.domain.registry.model.transfer.TransferData;
|
||||
import com.google.domain.registry.model.transfer.TransferData.TransferServerApproveEntity;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
import org.joda.money.Money;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that requests a transfer on a {@link DomainResource}.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.BadAuthInfoForResourceException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferRequestFlow.AlreadyPendingTransferException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferRequestFlow.MissingTransferRequestAuthInfoException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceTransferRequestFlow.ObjectAlreadySponsoredException}
|
||||
* @error {@link com.google.domain.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link DomainFlowUtils.BadPeriodUnitException}
|
||||
* @error {@link DomainFlowUtils.CurrencyUnitMismatchException}
|
||||
* @error {@link DomainFlowUtils.CurrencyValueScaleException}
|
||||
* @error {@link DomainFlowUtils.FeesMismatchException}
|
||||
* @error {@link DomainFlowUtils.FeesRequiredForPremiumNameException}
|
||||
* @error {@link DomainFlowUtils.PremiumNameBlockedException}
|
||||
* @error {@link DomainFlowUtils.UnsupportedFeeAttributeException}
|
||||
*/
|
||||
public class DomainTransferRequestFlow
|
||||
extends ResourceTransferRequestFlow<DomainResource, Transfer> {
|
||||
|
||||
/** The time when the transfer will be server approved if no other action happens first. */
|
||||
private DateTime automaticTransferTime;
|
||||
|
||||
/** A new one-time billing event for the renewal packaged as part of this transfer. */
|
||||
private BillingEvent.OneTime transferBillingEvent;
|
||||
|
||||
/** A new autorenew billing event starting at the transfer time. */
|
||||
private BillingEvent.Recurring gainingClientAutorenewEvent;
|
||||
|
||||
/** A new autorenew poll message starting at the transfer time. */
|
||||
private PollMessage.Autorenew gainingClientAutorenewPollMessage;
|
||||
|
||||
/** The amount that this transfer will cost due to the implied renew. */
|
||||
private Money renewCost;
|
||||
|
||||
/**
|
||||
* An optional extension from the client specifying how much they think the transfer should cost.
|
||||
*/
|
||||
private FeeTransferExtension feeTransfer;
|
||||
|
||||
@Override
|
||||
protected Duration getAutomaticTransferLength() {
|
||||
return Registry.get(existingResource.getTld()).getAutomaticTransferLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void initResourceTransferRequestFlow() {
|
||||
registerExtensions(FeeTransferExtension.class);
|
||||
feeTransfer = eppInput.getSingleExtension(FeeTransferExtension.class);
|
||||
// The "existingResource" field is loaded before this function is called, but it may be null if
|
||||
// the domain name specified is invalid or doesn't exist. If that's the case, simply exit
|
||||
// early, and ResourceMutateFlow will later throw ResourceToMutateDoesNotExistException.
|
||||
if (existingResource == null) {
|
||||
return;
|
||||
}
|
||||
Registry registry = Registry.get(existingResource.getTld());
|
||||
automaticTransferTime = now.plus(registry.getAutomaticTransferLength());
|
||||
renewCost = registry.getDomainRenewCost(targetId, command.getPeriod().getValue(), now);
|
||||
transferBillingEvent = new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.TRANSFER)
|
||||
.setTargetId(targetId)
|
||||
.setClientId(getClientId())
|
||||
.setCost(renewCost)
|
||||
.setPeriodYears(command.getPeriod().getValue())
|
||||
.setEventTime(automaticTransferTime)
|
||||
.setBillingTime(automaticTransferTime.plus(registry.getTransferGracePeriodLength()))
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
DateTime newExpirationTime = extendRegistrationWithCap(
|
||||
automaticTransferTime,
|
||||
existingResource.getRegistrationExpirationTime(),
|
||||
command.getPeriod().getValue());
|
||||
gainingClientAutorenewEvent = new BillingEvent.Recurring.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
.setTargetId(targetId)
|
||||
.setClientId(gainingClient.getId())
|
||||
.setEventTime(newExpirationTime)
|
||||
.setRecurrenceEndTime(END_OF_TIME)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
gainingClientAutorenewPollMessage = new PollMessage.Autorenew.Builder()
|
||||
.setTargetId(targetId)
|
||||
.setClientId(gainingClient.getId())
|
||||
.setEventTime(newExpirationTime)
|
||||
.setAutorenewEndTime(END_OF_TIME)
|
||||
.setMsg("Domain was auto-renewed.")
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void verifyTransferRequestIsAllowed() throws EppException {
|
||||
verifyUnitIsYears(command.getPeriod());
|
||||
if (!superuser) {
|
||||
verifyPremiumNameIsNotBlocked(targetId, existingResource.getTld(), getClientId());
|
||||
}
|
||||
validateFeeChallenge(targetId, existingResource.getTld(), feeTransfer, renewCost);
|
||||
checkAllowedAccessToTld(getAllowedTlds(), existingResource.getTld());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ImmutableList<? extends ResponseExtension> getTransferResponseExtensions() {
|
||||
if (feeTransfer != null) {
|
||||
return ImmutableList.of(new FeeTransferResponseExtension.Builder()
|
||||
.setCurrency(renewCost.getCurrencyUnit())
|
||||
.setFee(ImmutableList.of(Fee.create(renewCost.getAmount(), "renew")))
|
||||
.build());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setTransferDataProperties(TransferData.Builder builder) {
|
||||
builder
|
||||
.setServerApproveBillingEvent(Ref.create(transferBillingEvent))
|
||||
.setServerApproveAutorenewEvent(Ref.create(gainingClientAutorenewEvent))
|
||||
.setServerApproveAutorenewPollMessage(Ref.create(gainingClientAutorenewPollMessage))
|
||||
.setExtendedRegistrationYears(command.getPeriod().getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* When a transfer is requested, schedule a billing event and poll message for the automatic
|
||||
* approval case.
|
||||
* <p>
|
||||
* Note that the action time is AUTOMATIC_TRANSFER_DAYS in the future, matching the server policy
|
||||
* on automated approval of transfers. There is no equivalent grace period added; if the transfer
|
||||
* is implicitly approved, the resource will project a grace period on itself.
|
||||
*/
|
||||
@Override
|
||||
protected Set<Key<? extends TransferServerApproveEntity>> getTransferServerApproveEntities() {
|
||||
ofy().save().<Object>entities(
|
||||
transferBillingEvent, gainingClientAutorenewEvent, gainingClientAutorenewPollMessage);
|
||||
// If there will be an autorenew between now and the automatic transfer time, and if the
|
||||
// autorenew grace period length is long enough that the domain will still be within it at the
|
||||
// automatic transfer time, then the transfer will subsume the autorenew so we need to write out
|
||||
// a cancellation for it.
|
||||
Set<Key<? extends TransferServerApproveEntity>> serverApproveEntities = new HashSet<>();
|
||||
DateTime expirationTime = existingResource.getRegistrationExpirationTime();
|
||||
Registry registry = Registry.get(existingResource.getTld());
|
||||
if (automaticTransferTime.isAfter(expirationTime) && automaticTransferTime.isBefore(
|
||||
expirationTime.plus(registry.getAutoRenewGracePeriodLength()))) {
|
||||
BillingEvent.Cancellation autorenewCancellation = new BillingEvent.Cancellation.Builder()
|
||||
.setReason(Reason.RENEW)
|
||||
.setFlags(ImmutableSet.of(Flag.AUTO_RENEW))
|
||||
.setTargetId(targetId)
|
||||
.setClientId(existingResource.getCurrentSponsorClientId())
|
||||
.setEventTime(automaticTransferTime)
|
||||
.setBillingTime(expirationTime.plus(registry.getAutoRenewGracePeriodLength()))
|
||||
.setRecurringEventRef(existingResource.getAutorenewBillingEvent())
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
ofy().save().entity(autorenewCancellation);
|
||||
serverApproveEntities.add(Key.create(autorenewCancellation));
|
||||
}
|
||||
serverApproveEntities.add(Key.create(transferBillingEvent));
|
||||
serverApproveEntities.add(Key.create(gainingClientAutorenewEvent));
|
||||
serverApproveEntities.add(Key.create(gainingClientAutorenewPollMessage));
|
||||
return serverApproveEntities;
|
||||
}
|
||||
|
||||
/** Close the old autorenew billing event and save a new one. */
|
||||
@Override
|
||||
protected final void modifyRelatedResources() throws EppException {
|
||||
// End the old autorenew event and poll message at the implicit transfer time. This may delete
|
||||
// the poll message if it has no events left.
|
||||
//
|
||||
// Note that this is still left on the domain as the autorenewBillingEvent because it is still
|
||||
// the current autorenew event until the transfer happens. If you read the domain after the
|
||||
// transfer occurs, then the logic in cloneProjectedAtTime() will move the
|
||||
// serverApproveAutoRenewEvent into the autoRenewEvent field.
|
||||
updateAutorenewRecurrenceEndTime(existingResource, automaticTransferTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_TRANSFER_REQUEST;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final Period getCommandPeriod() {
|
||||
return command.getPeriod();
|
||||
}
|
||||
}
|
167
java/google/registry/flows/domain/DomainUpdateFlow.java
Normal file
167
java/google/registry/flows/domain/DomainUpdateFlow.java
Normal 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.flows.domain;
|
||||
|
||||
import static com.google.common.collect.Sets.symmetricDifference;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.earliestOf;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.domain.registry.dns.DnsQueue;
|
||||
import com.google.domain.registry.model.billing.BillingEvent;
|
||||
import com.google.domain.registry.model.billing.BillingEvent.Reason;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.domain.DomainResource.Builder;
|
||||
import com.google.domain.registry.model.domain.GracePeriod;
|
||||
import com.google.domain.registry.model.domain.metadata.MetadataExtension;
|
||||
import com.google.domain.registry.model.domain.rgp.GracePeriodStatus;
|
||||
import com.google.domain.registry.model.domain.secdns.SecDnsUpdateExtension;
|
||||
import com.google.domain.registry.model.eppcommon.StatusValue;
|
||||
import com.google.domain.registry.model.registry.Registry;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that updates a domain resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.EppException.UnimplementedExtensionException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCreateOrMutateFlow.OnlyToolCanPassMetadataException}
|
||||
* @error {@link com.google.domain.registry.flows.domain.DomainFlowUtils.NotAuthorizedForTldException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceUpdateFlow.AddRemoveSameValueEppException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException}
|
||||
* @error {@link com.google.domain.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link BaseDomainUpdateFlow.EmptySecDnsUpdateException}
|
||||
* @error {@link BaseDomainUpdateFlow.MaxSigLifeChangeNotSupportedException}
|
||||
* @error {@link BaseDomainUpdateFlow.SecDnsAllUsageException}
|
||||
* @error {@link BaseDomainUpdateFlow.UrgentAttributeNotSupportedException}
|
||||
* @error {@link DomainFlowUtils.DuplicateContactForRoleException}
|
||||
* @error {@link DomainFlowUtils.LinkedResourceDoesNotExistException}
|
||||
* @error {@link DomainFlowUtils.LinkedResourceInPendingDeleteProhibitsOperationException}
|
||||
* @error {@link DomainFlowUtils.MissingAdminContactException}
|
||||
* @error {@link DomainFlowUtils.MissingContactTypeException}
|
||||
* @error {@link DomainFlowUtils.MissingTechnicalContactException}
|
||||
* @error {@link DomainFlowUtils.NameserverNotAllowedException}
|
||||
* @error {@link DomainFlowUtils.RegistrantNotAllowedException}
|
||||
* @error {@link DomainFlowUtils.TooManyDsRecordsException}
|
||||
* @error {@link DomainFlowUtils.TooManyNameserversException}
|
||||
*/
|
||||
public class DomainUpdateFlow extends BaseDomainUpdateFlow<DomainResource, Builder> {
|
||||
|
||||
@Override
|
||||
protected void initDomainUpdateFlow() {
|
||||
registerExtensions(SecDnsUpdateExtension.class, MetadataExtension.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Builder setDomainUpdateProperties(Builder builder) {
|
||||
// Check if the domain is currently in the sunrush add grace period.
|
||||
Optional<GracePeriod> sunrushAddGracePeriod = Iterables.tryFind(
|
||||
existingResource.getGracePeriods(),
|
||||
new Predicate<GracePeriod>() {
|
||||
@Override
|
||||
public boolean apply(GracePeriod gracePeriod) {
|
||||
return gracePeriod.isSunrushAddGracePeriod();
|
||||
}});
|
||||
|
||||
// If this domain is currently in the sunrush add grace period, and we're updating it in a way
|
||||
// that will cause it to now get delegated (either by setting nameservers, or by removing a
|
||||
// clientHold or serverHold), then that will remove the sunrush add grace period and convert
|
||||
// that to a standard add grace period.
|
||||
DomainResource updatedDomain = builder.build();
|
||||
builder = updatedDomain.asBuilder();
|
||||
if (sunrushAddGracePeriod.isPresent() && updatedDomain.shouldPublishToDns()) {
|
||||
// Remove the sunrush grace period and write a billing event cancellation for it.
|
||||
builder.removeGracePeriod(sunrushAddGracePeriod.get());
|
||||
BillingEvent.Cancellation billingEventCancellation = BillingEvent.Cancellation
|
||||
.forGracePeriod(sunrushAddGracePeriod.get(), historyEntry, targetId);
|
||||
|
||||
// Compute the expiration time of the add grace period. We will not allow it to be after the
|
||||
// sunrush add grace period expiration time (i.e. you can't get extra add grace period by
|
||||
// setting a nameserver).
|
||||
DateTime addGracePeriodExpirationTime = earliestOf(
|
||||
now.plus(Registry.get(existingResource.getTld()).getAddGracePeriodLength()),
|
||||
sunrushAddGracePeriod.get().getExpirationTime());
|
||||
|
||||
// Create a new billing event for the add grace period. Note that we do this even if it would
|
||||
// occur at the same time as the sunrush add grace period, as the event time will differ
|
||||
// between them.
|
||||
BillingEvent.OneTime originalAddEvent =
|
||||
sunrushAddGracePeriod.get().getOneTimeBillingEvent().get();
|
||||
BillingEvent.OneTime billingEvent = new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.CREATE)
|
||||
.setTargetId(targetId)
|
||||
.setFlags(originalAddEvent.getFlags())
|
||||
.setClientId(sunrushAddGracePeriod.get().getClientId())
|
||||
.setCost(originalAddEvent.getCost())
|
||||
.setPeriodYears(originalAddEvent.getPeriodYears())
|
||||
.setEventTime(now)
|
||||
.setBillingTime(addGracePeriodExpirationTime)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
|
||||
// Set the add grace period on the domain.
|
||||
builder.addGracePeriod(GracePeriod.forBillingEvent(GracePeriodStatus.ADD, billingEvent));
|
||||
|
||||
// Save the billing events.
|
||||
ofy().save().entities(billingEvent, billingEventCancellation);
|
||||
}
|
||||
|
||||
return builder;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final void modifyRelatedResources() {
|
||||
// Determine the status changes, and filter to server statuses.
|
||||
// If any of these statuses have been added or removed, bill once.
|
||||
if (metadataExtension != null && metadataExtension.getRequestedByRegistrar()) {
|
||||
Set<StatusValue> statusDifferences =
|
||||
symmetricDifference(existingResource.getStatusValues(), newResource.getStatusValues());
|
||||
if (Iterables.any(statusDifferences, new Predicate<StatusValue>() {
|
||||
@Override
|
||||
public boolean apply(StatusValue statusValue) {
|
||||
return statusValue.isChargedStatus();
|
||||
}})) {
|
||||
BillingEvent.OneTime billingEvent = new BillingEvent.OneTime.Builder()
|
||||
.setReason(Reason.SERVER_STATUS)
|
||||
.setTargetId(targetId)
|
||||
.setClientId(getClientId())
|
||||
.setCost(Registry.get(existingResource.getTld()).getServerStatusChangeCost())
|
||||
.setEventTime(now)
|
||||
.setBillingTime(now)
|
||||
.setParent(historyEntry)
|
||||
.build();
|
||||
ofy().save().entity(billingEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enqueueTasks() {
|
||||
DnsQueue.create().addDomainRefreshTask(existingResource.getFullyQualifiedDomainName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.DOMAIN_UPDATE;
|
||||
}
|
||||
}
|
45
java/google/registry/flows/host/HostCheckFlow.java
Normal file
45
java/google/registry/flows/host/HostCheckFlow.java
Normal 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.flows.host;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.checkResourcesExist;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.flows.ResourceCheckFlow;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData.HostCheck;
|
||||
import com.google.domain.registry.model.eppoutput.CheckData.HostCheckData;
|
||||
import com.google.domain.registry.model.host.HostCommand.Check;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow that checks whether a host can be provisioned.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCheckFlow.TooManyResourceChecksException}
|
||||
*/
|
||||
public class HostCheckFlow extends ResourceCheckFlow<HostResource, Check> {
|
||||
@Override
|
||||
protected CheckData getCheckData() {
|
||||
Set<String> existingIds = checkResourcesExist(resourceClass, targetIds, now);
|
||||
ImmutableList.Builder<HostCheck> checks = new ImmutableList.Builder<>();
|
||||
for (String id : targetIds) {
|
||||
boolean unused = !existingIds.contains(id);
|
||||
checks.add(HostCheck.create(unused, id, unused ? null : "In use"));
|
||||
}
|
||||
return HostCheckData.create(checks.build());
|
||||
}
|
||||
}
|
143
java/google/registry/flows/host/HostCreateFlow.java
Normal file
143
java/google/registry/flows/host/HostCreateFlow.java
Normal 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.flows.host;
|
||||
|
||||
import static com.google.domain.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain;
|
||||
import static com.google.domain.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static com.google.domain.registry.flows.host.HostFlowUtils.verifyDomainIsSameRegistrar;
|
||||
import static com.google.domain.registry.model.EppResourceUtils.createContactHostRoid;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.domain.registry.dns.DnsQueue;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||
import com.google.domain.registry.flows.EppException.RequiredParameterMissingException;
|
||||
import com.google.domain.registry.flows.ResourceCreateFlow;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.eppoutput.CreateData.HostCreateData;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.host.HostCommand.Create;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
import com.google.domain.registry.model.host.HostResource.Builder;
|
||||
import com.google.domain.registry.model.ofy.ObjectifyService;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
/**
|
||||
* An EPP flow that creates a new host resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.EppXmlTransformer.IpAddressVersionMismatchException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceCreateFlow.ResourceAlreadyExistsException}
|
||||
* @error {@link HostFlowUtils.HostNameTooLongException}
|
||||
* @error {@link HostFlowUtils.HostNameTooShallowException}
|
||||
* @error {@link HostFlowUtils.InvalidHostNameException}
|
||||
* @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException}
|
||||
* @error {@link SubordinateHostMustHaveIpException}
|
||||
* @error {@link UnexpectedExternalHostIpException}
|
||||
*/
|
||||
public class HostCreateFlow extends ResourceCreateFlow<HostResource, Builder, Create> {
|
||||
|
||||
/**
|
||||
* The superordinate domain of the host object if creating an in-bailiwick host, or null if
|
||||
* creating an external host. This is looked up before we actually create the Host object so that
|
||||
* we can detect error conditions earlier. By the time {@link #setCreateProperties} is called
|
||||
* (where this reference is actually used), we no longer have the ability to return an
|
||||
* {@link EppException}.
|
||||
*
|
||||
* <p>The general model of these classes is to do validation of parameters up front before we get
|
||||
* to the actual object creation, which is why this class looks up and stores the superordinate
|
||||
* domain ahead of time.
|
||||
*/
|
||||
private Optional<Ref<DomainResource>> superordinateDomain;
|
||||
|
||||
@Override
|
||||
protected void initResourceCreateOrMutateFlow() throws EppException {
|
||||
superordinateDomain = Optional.fromNullable(lookupSuperordinateDomain(
|
||||
validateHostName(command.getFullyQualifiedHostName()), now));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String createFlowRepoId() {
|
||||
return createContactHostRoid(ObjectifyService.allocateId());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verifyCreateIsAllowed() throws EppException {
|
||||
verifyDomainIsSameRegistrar(superordinateDomain.orNull(), getClientId());
|
||||
boolean willBeSubordinate = superordinateDomain.isPresent();
|
||||
boolean hasIpAddresses = !isNullOrEmpty(command.getInetAddresses());
|
||||
if (willBeSubordinate != hasIpAddresses) {
|
||||
// Subordinate hosts must have ip addresses and external hosts must not have them.
|
||||
throw willBeSubordinate
|
||||
? new SubordinateHostMustHaveIpException()
|
||||
: new UnexpectedExternalHostIpException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setCreateProperties(Builder builder) {
|
||||
if (superordinateDomain.isPresent()) {
|
||||
builder.setSuperordinateDomain(superordinateDomain.get());
|
||||
}
|
||||
}
|
||||
|
||||
/** Modify any other resources that need to be informed of this create. */
|
||||
@Override
|
||||
protected void modifyCreateRelatedResources() {
|
||||
if (superordinateDomain.isPresent()) {
|
||||
ofy().save().entity(superordinateDomain.get().get().asBuilder()
|
||||
.addSubordinateHost(command.getFullyQualifiedHostName())
|
||||
.build());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void enqueueTasks() {
|
||||
// Only update DNS if this is a subordinate host. External hosts have no glue to write, so they
|
||||
// are only written as NS records from the referencing domain.
|
||||
if (superordinateDomain.isPresent()) {
|
||||
DnsQueue.create().addHostRefreshTask(newResource.getFullyQualifiedHostName());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.HOST_CREATE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected EppOutput getOutput() {
|
||||
return createOutput(Success,
|
||||
HostCreateData.create(newResource.getFullyQualifiedHostName(), now));
|
||||
}
|
||||
|
||||
/** Subordinate hosts must have an ip address. */
|
||||
static class SubordinateHostMustHaveIpException extends RequiredParameterMissingException {
|
||||
public SubordinateHostMustHaveIpException() {
|
||||
super("Subordinate hosts must have an ip address");
|
||||
}
|
||||
}
|
||||
|
||||
/** External hosts must not have ip addresses. */
|
||||
static class UnexpectedExternalHostIpException extends ParameterValueRangeErrorException {
|
||||
public UnexpectedExternalHostIpException() {
|
||||
super("External hosts must not have ip addresses");
|
||||
}
|
||||
}
|
||||
}
|
87
java/google/registry/flows/host/HostDeleteFlow.java
Normal file
87
java/google/registry/flows/host/HostDeleteFlow.java
Normal file
|
@ -0,0 +1,87 @@
|
|||
// 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.flows.host;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.queryDomainsUsingResource;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.domain.registry.config.RegistryEnvironment;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.ResourceAsyncDeleteFlow;
|
||||
import com.google.domain.registry.flows.async.AsyncFlowUtils;
|
||||
import com.google.domain.registry.flows.async.DeleteEppResourceAction;
|
||||
import com.google.domain.registry.flows.async.DeleteHostResourceAction;
|
||||
import com.google.domain.registry.model.domain.DomainBase;
|
||||
import com.google.domain.registry.model.domain.ReferenceUnion;
|
||||
import com.google.domain.registry.model.host.HostCommand.Delete;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
import com.google.domain.registry.model.host.HostResource.Builder;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
|
||||
/**
|
||||
* An EPP flow that deletes a host resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceAsyncDeleteFlow.ResourceToDeleteIsReferencedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
||||
*/
|
||||
public class HostDeleteFlow extends ResourceAsyncDeleteFlow<HostResource, Builder, Delete> {
|
||||
|
||||
/** In {@link #isLinkedForFailfast}, check this (arbitrary) number of resources from the query. */
|
||||
private static final int FAILFAST_CHECK_COUNT = 5;
|
||||
|
||||
@Override
|
||||
protected boolean isLinkedForFailfast(final ReferenceUnion<HostResource> ref) {
|
||||
// Query for the first few linked domains, and if found, actually load them. The query is
|
||||
// eventually consistent and so might be very stale, but the direct load will not be stale,
|
||||
// just non-transactional. If we find at least one actual reference then we can reliably
|
||||
// fail. If we don't find any, we can't trust the query and need to do the full mapreduce.
|
||||
return Iterables.any(
|
||||
ofy().load().keys(
|
||||
queryDomainsUsingResource(
|
||||
HostResource.class, ref.getLinked(), now, FAILFAST_CHECK_COUNT)).values(),
|
||||
new Predicate<DomainBase>() {
|
||||
@Override
|
||||
public boolean apply(DomainBase domain) {
|
||||
return domain.getNameservers().contains(ref);
|
||||
}});
|
||||
}
|
||||
|
||||
/** Enqueues a host resource deletion on the mapreduce queue. */
|
||||
@Override
|
||||
protected final void enqueueTasks() throws EppException {
|
||||
AsyncFlowUtils.enqueueMapreduceAction(
|
||||
DeleteHostResourceAction.class,
|
||||
ImmutableMap.of(
|
||||
DeleteEppResourceAction.PARAM_RESOURCE_KEY,
|
||||
Key.create(existingResource).getString(),
|
||||
DeleteEppResourceAction.PARAM_REQUESTING_CLIENT_ID,
|
||||
getClientId(),
|
||||
DeleteEppResourceAction.PARAM_IS_SUPERUSER,
|
||||
Boolean.toString(superuser)),
|
||||
RegistryEnvironment.get().config().getAsyncDeleteFlowMapreduceDelay());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.HOST_PENDING_DELETE;
|
||||
}
|
||||
}
|
143
java/google/registry/flows/host/HostFlowUtils.java
Normal file
143
java/google/registry/flows/host/HostFlowUtils.java
Normal 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.flows.host;
|
||||
|
||||
import static com.google.domain.registry.model.EppResourceUtils.isActive;
|
||||
import static com.google.domain.registry.model.EppResourceUtils.loadByUniqueId;
|
||||
import static com.google.domain.registry.model.registry.Registries.findTldForName;
|
||||
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.net.InternetDomainName;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.AuthorizationErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ObjectDoesNotExistException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/** Static utility functions for host flows. */
|
||||
public class HostFlowUtils {
|
||||
|
||||
/** Checks that a host name is valid. */
|
||||
static InternetDomainName validateHostName(String name) throws EppException {
|
||||
if (name == null) {
|
||||
return null;
|
||||
}
|
||||
if (name.length() > 253) {
|
||||
throw new HostNameTooLongException();
|
||||
}
|
||||
try {
|
||||
InternetDomainName hostName = InternetDomainName.from(name);
|
||||
// Checks whether a hostname is deep enough. Technically a host can be just one under a
|
||||
// public suffix (e.g. example.com) but we require by policy that it has to be at least one
|
||||
// part beyond that (e.g. ns1.example.com). The public suffix list includes all current
|
||||
// ccTlds, so this check requires 4+ parts if it's a ccTld that doesn't delegate second
|
||||
// level domains, such as .co.uk. But the list does not include new tlds, so in that case
|
||||
// we just ensure 3+ parts. In the particular case where our own tld has a '.' in it, we know
|
||||
// that there need to be 4 parts as well.
|
||||
if (hostName.isUnderPublicSuffix()) {
|
||||
if (hostName.parent().isUnderPublicSuffix()) {
|
||||
return hostName;
|
||||
}
|
||||
} else {
|
||||
// We need to know how many parts the hostname has beyond the public suffix, but we don't
|
||||
// know what the public suffix is. If the host is in bailiwick and we are hosting a
|
||||
// multipart "tld" like .co.uk the publix suffix might be 2 parts. Otherwise it's an
|
||||
// unrecognized tld that's not on the public suffix list, so assume the tld alone is the
|
||||
// public suffix.
|
||||
Optional<InternetDomainName> tldParsed = findTldForName(hostName);
|
||||
int suffixSize = tldParsed.isPresent() ? tldParsed.get().parts().size() : 1;
|
||||
if (hostName.parts().size() >= suffixSize + 2) {
|
||||
return hostName;
|
||||
}
|
||||
}
|
||||
throw new HostNameTooShallowException();
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new InvalidHostNameException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Return the {@link DomainResource} this host is subordinate to, or null for external hosts. */
|
||||
static Ref<DomainResource> lookupSuperordinateDomain(
|
||||
InternetDomainName hostName, DateTime now) throws EppException {
|
||||
Optional<InternetDomainName> tldParsed = findTldForName(hostName);
|
||||
if (!tldParsed.isPresent()) {
|
||||
// This is an host on a TLD we don't run, therefore obviously external, so we are done.
|
||||
return null;
|
||||
}
|
||||
|
||||
// This is a subordinate host
|
||||
@SuppressWarnings("deprecation")
|
||||
String domainName = Joiner.on('.').join(Iterables.skip(
|
||||
hostName.parts(), hostName.parts().size() - (tldParsed.get().parts().size() + 1)));
|
||||
DomainResource superordinateDomain = loadByUniqueId(DomainResource.class, domainName, now);
|
||||
if (superordinateDomain == null || !isActive(superordinateDomain, now)) {
|
||||
throw new SuperordinateDomainDoesNotExistException(domainName);
|
||||
}
|
||||
return Ref.create(superordinateDomain);
|
||||
}
|
||||
|
||||
/** Superordinate domain for this hostname does not exist. */
|
||||
static class SuperordinateDomainDoesNotExistException extends ObjectDoesNotExistException {
|
||||
public SuperordinateDomainDoesNotExistException(String domainName) {
|
||||
super(DomainResource.class, domainName);
|
||||
}
|
||||
}
|
||||
|
||||
/** Ensure that the superordinate domain is sponsored by the provided clientId. */
|
||||
static void verifyDomainIsSameRegistrar(
|
||||
Ref<DomainResource> superordinateDomain,
|
||||
String clientId) throws EppException {
|
||||
if (superordinateDomain != null
|
||||
&& !clientId.equals(superordinateDomain.get().getCurrentSponsorClientId())) {
|
||||
throw new HostDomainNotOwnedException();
|
||||
}
|
||||
}
|
||||
|
||||
/** Domain for host is sponsored by another registrar. */
|
||||
static class HostDomainNotOwnedException extends AuthorizationErrorException {
|
||||
public HostDomainNotOwnedException() {
|
||||
super("Domain for host is sponsored by another registrar");
|
||||
}
|
||||
}
|
||||
|
||||
/** Host names are limited to 253 characters. */
|
||||
static class HostNameTooLongException extends ParameterValueRangeErrorException {
|
||||
public HostNameTooLongException() {
|
||||
super("Host names are limited to 253 characters");
|
||||
}
|
||||
}
|
||||
|
||||
/** Host names must be at least two levels below the public suffix. */
|
||||
static class HostNameTooShallowException extends ParameterValuePolicyErrorException {
|
||||
public HostNameTooShallowException() {
|
||||
super("Host names must be at least two levels below the public suffix");
|
||||
}
|
||||
}
|
||||
|
||||
/** Invalid host name. */
|
||||
static class InvalidHostNameException extends ParameterValueSyntaxErrorException {
|
||||
public InvalidHostNameException() {
|
||||
super("Invalid host name");
|
||||
}
|
||||
}
|
||||
}
|
26
java/google/registry/flows/host/HostInfoFlow.java
Normal file
26
java/google/registry/flows/host/HostInfoFlow.java
Normal file
|
@ -0,0 +1,26 @@
|
|||
// 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.flows.host;
|
||||
|
||||
import com.google.domain.registry.flows.ResourceInfoFlow;
|
||||
import com.google.domain.registry.model.host.HostCommand;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
|
||||
/**
|
||||
* An EPP flow that reads a host.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceQueryFlow.ResourceToQueryDoesNotExistException}
|
||||
*/
|
||||
public class HostInfoFlow extends ResourceInfoFlow<HostResource, HostCommand.Info> {}
|
235
java/google/registry/flows/host/HostUpdateFlow.java
Normal file
235
java/google/registry/flows/host/HostUpdateFlow.java
Normal file
|
@ -0,0 +1,235 @@
|
|||
// 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.flows.host;
|
||||
|
||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||
import static com.google.domain.registry.flows.host.HostFlowUtils.lookupSuperordinateDomain;
|
||||
import static com.google.domain.registry.flows.host.HostFlowUtils.validateHostName;
|
||||
import static com.google.domain.registry.flows.host.HostFlowUtils.verifyDomainIsSameRegistrar;
|
||||
import static com.google.domain.registry.model.index.ForeignKeyIndex.loadAndGetReference;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.CollectionUtils.isNullOrEmpty;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.domain.registry.dns.DnsQueue;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.ObjectAlreadyExistsException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueRangeErrorException;
|
||||
import com.google.domain.registry.flows.EppException.RequiredParameterMissingException;
|
||||
import com.google.domain.registry.flows.EppException.StatusProhibitsOperationException;
|
||||
import com.google.domain.registry.flows.ResourceUpdateFlow;
|
||||
import com.google.domain.registry.flows.async.AsyncFlowUtils;
|
||||
import com.google.domain.registry.flows.async.DnsRefreshForHostRenameAction;
|
||||
import com.google.domain.registry.model.domain.DomainResource;
|
||||
import com.google.domain.registry.model.host.HostCommand.Update;
|
||||
import com.google.domain.registry.model.host.HostResource;
|
||||
import com.google.domain.registry.model.host.HostResource.Builder;
|
||||
import com.google.domain.registry.model.index.ForeignKeyIndex;
|
||||
import com.google.domain.registry.model.reporting.HistoryEntry;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.Ref;
|
||||
|
||||
import org.joda.time.Duration;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An EPP flow that updates a host resource.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.ResourceFlowUtils.ResourceNotOwnedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceMutateFlow.ResourceToMutateDoesNotExistException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceUpdateFlow.ResourceHasClientUpdateProhibitedException}
|
||||
* @error {@link com.google.domain.registry.flows.ResourceUpdateFlow.StatusNotClientSettableException}
|
||||
* @error {@link com.google.domain.registry.flows.SingleResourceFlow.ResourceStatusProhibitsOperationException}
|
||||
* @error {@link HostFlowUtils.HostNameTooShallowException}
|
||||
* @error {@link HostFlowUtils.InvalidHostNameException}
|
||||
* @error {@link HostFlowUtils.SuperordinateDomainDoesNotExistException}
|
||||
* @error {@link CannotAddIpToExternalHostException}
|
||||
* @error {@link CannotRemoveSubordinateHostLastIpException}
|
||||
* @error {@link HostAlreadyExistsException}
|
||||
* @error {@link RenameHostToExternalRemoveIpException}
|
||||
* @error {@link RenameHostToSubordinateRequiresIpException}
|
||||
*/
|
||||
public class HostUpdateFlow extends ResourceUpdateFlow<HostResource, Builder, Update> {
|
||||
|
||||
private Ref<DomainResource> superordinateDomain;
|
||||
|
||||
private String oldHostName;
|
||||
private String newHostName;
|
||||
private boolean isHostRename;
|
||||
|
||||
@Override
|
||||
protected void initResourceCreateOrMutateFlow() throws EppException {
|
||||
String suppliedNewHostName = command.getInnerChange().getFullyQualifiedHostName();
|
||||
isHostRename = suppliedNewHostName != null;
|
||||
oldHostName = targetId;
|
||||
newHostName = firstNonNull(suppliedNewHostName, oldHostName);
|
||||
superordinateDomain =
|
||||
lookupSuperordinateDomain(validateHostName(newHostName), now);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verifyUpdateIsAllowed() throws EppException {
|
||||
verifyDomainIsSameRegistrar(superordinateDomain, getClientId());
|
||||
if (isHostRename
|
||||
&& loadAndGetReference(HostResource.class, newHostName, now) != null) {
|
||||
throw new HostAlreadyExistsException(newHostName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void verifyNewUpdatedStateIsAllowed() throws EppException {
|
||||
boolean wasExternal = existingResource.getSuperordinateDomain() == null;
|
||||
boolean wasSubordinate = !wasExternal;
|
||||
boolean willBeExternal = superordinateDomain == null;
|
||||
boolean willBeSubordinate = !willBeExternal;
|
||||
boolean newResourceHasIps = !isNullOrEmpty(newResource.getInetAddresses());
|
||||
boolean commandAddsIps = !isNullOrEmpty(command.getInnerAdd().getInetAddresses());
|
||||
// These checks are order-dependent. For example a subordinate-to-external rename that adds new
|
||||
// ips should hit the first exception, whereas one that only fails to remove the existing ips
|
||||
// should hit the second.
|
||||
if (willBeExternal && commandAddsIps) {
|
||||
throw new CannotAddIpToExternalHostException();
|
||||
}
|
||||
if (wasSubordinate && willBeExternal && newResourceHasIps) {
|
||||
throw new RenameHostToExternalRemoveIpException();
|
||||
}
|
||||
if (wasExternal && willBeSubordinate && !commandAddsIps) {
|
||||
throw new RenameHostToSubordinateRequiresIpException();
|
||||
}
|
||||
if (willBeSubordinate && !newResourceHasIps) {
|
||||
throw new CannotRemoveSubordinateHostLastIpException();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Builder setUpdateProperties(Builder builder) {
|
||||
// The superordinateDomain can be null if the new name is external.
|
||||
// Note that the value of superordinateDomain is projected to the current time inside of
|
||||
// the lookupSuperordinateDomain(...) call above, so that it will never be stale.
|
||||
builder.setSuperordinateDomain(superordinateDomain);
|
||||
builder.setLastSuperordinateChange(superordinateDomain == null ? null : now);
|
||||
// Rely on the host's cloneProjectedAtTime() method to handle setting of transfer data.
|
||||
return builder.build().cloneProjectedAtTime(now).asBuilder();
|
||||
}
|
||||
|
||||
/** Keep the {@link ForeignKeyIndex} for this host up to date. */
|
||||
@Override
|
||||
protected void modifyRelatedResources() {
|
||||
if (isHostRename) {
|
||||
// Update the foreign key for the old host name.
|
||||
ofy().save().entity(ForeignKeyIndex.create(existingResource, now));
|
||||
// Save the foreign key for the new host name.
|
||||
ofy().save().entity(ForeignKeyIndex.create(newResource, newResource.getDeletionTime()));
|
||||
updateSuperordinateDomains();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enqueueTasks() throws EppException {
|
||||
DnsQueue dnsQueue = DnsQueue.create();
|
||||
// Only update DNS for subordinate hosts. External hosts have no glue to write, so they
|
||||
// are only written as NS records from the referencing domain.
|
||||
if (existingResource.getSuperordinateDomain() != null) {
|
||||
dnsQueue.addHostRefreshTask(oldHostName);
|
||||
}
|
||||
// In case of a rename, there are many updates we need to queue up.
|
||||
if (isHostRename) {
|
||||
// If the renamed host is also subordinate, then we must enqueue an update to write the new
|
||||
// glue.
|
||||
if (newResource.getSuperordinateDomain() != null) {
|
||||
dnsQueue.addHostRefreshTask(newHostName);
|
||||
}
|
||||
// We must also enqueue updates for all domains that use this host as their nameserver so
|
||||
// that their NS records can be updated to point at the new name.
|
||||
AsyncFlowUtils.enqueueMapreduceAction(
|
||||
DnsRefreshForHostRenameAction.class,
|
||||
ImmutableMap.of(
|
||||
DnsRefreshForHostRenameAction.PARAM_HOST_KEY,
|
||||
Key.create(existingResource).getString()),
|
||||
Duration.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final HistoryEntry.Type getHistoryEntryType() {
|
||||
return HistoryEntry.Type.HOST_UPDATE;
|
||||
}
|
||||
|
||||
private void updateSuperordinateDomains() {
|
||||
Ref<DomainResource> oldSuperordinateDomain = existingResource.getSuperordinateDomain();
|
||||
if (oldSuperordinateDomain != null || superordinateDomain != null) {
|
||||
if (Objects.equals(oldSuperordinateDomain, superordinateDomain)) {
|
||||
ofy().save().entity(oldSuperordinateDomain.get().asBuilder()
|
||||
.removeSubordinateHost(oldHostName)
|
||||
.addSubordinateHost(newHostName)
|
||||
.build());
|
||||
} else {
|
||||
if (oldSuperordinateDomain != null) {
|
||||
ofy().save().entity(
|
||||
oldSuperordinateDomain.get()
|
||||
.asBuilder()
|
||||
.removeSubordinateHost(oldHostName)
|
||||
.build());
|
||||
}
|
||||
if (superordinateDomain != null) {
|
||||
ofy().save().entity(
|
||||
superordinateDomain.get()
|
||||
.asBuilder()
|
||||
.addSubordinateHost(newHostName)
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Host with specified name already exists. */
|
||||
static class HostAlreadyExistsException extends ObjectAlreadyExistsException {
|
||||
public HostAlreadyExistsException(String hostName) {
|
||||
super(String.format("Object with given ID (%s) already exists", hostName));
|
||||
}
|
||||
}
|
||||
|
||||
/** Cannot add ip addresses to an external host. */
|
||||
static class CannotAddIpToExternalHostException extends ParameterValueRangeErrorException {
|
||||
public CannotAddIpToExternalHostException() {
|
||||
super("Cannot add ip addresses to external hosts");
|
||||
}
|
||||
}
|
||||
|
||||
/** Cannot remove all ip addresses from a subordinate host. */
|
||||
static class CannotRemoveSubordinateHostLastIpException
|
||||
extends StatusProhibitsOperationException {
|
||||
public CannotRemoveSubordinateHostLastIpException() {
|
||||
super("Cannot remove all ip addresses from a subordinate host");
|
||||
}
|
||||
}
|
||||
|
||||
/** Host rename from external to subordinate must also add an ip addresses. */
|
||||
static class RenameHostToSubordinateRequiresIpException
|
||||
extends RequiredParameterMissingException {
|
||||
public RenameHostToSubordinateRequiresIpException() {
|
||||
super("Host rename from external to subordinate must also add an ip address");
|
||||
}
|
||||
}
|
||||
|
||||
/** Host rename from subordinate to external must also remove all ip addresses. */
|
||||
static class RenameHostToExternalRemoveIpException extends ParameterValueRangeErrorException {
|
||||
public RenameHostToExternalRemoveIpException() {
|
||||
super("Host rename from subordinate to external must also remove all ip addresses");
|
||||
}
|
||||
}
|
||||
}
|
148
java/google/registry/flows/poll/PollAckFlow.java
Normal file
148
java/google/registry/flows/poll/PollAckFlow.java
Normal file
|
@ -0,0 +1,148 @@
|
|||
// 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.flows.poll;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.Success;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.SuccessWithNoMessages;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.DateTimeUtils.isBeforeOrAt;
|
||||
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.AuthorizationErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ObjectDoesNotExistException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import com.google.domain.registry.flows.EppException.RequiredParameterMissingException;
|
||||
import com.google.domain.registry.flows.TransactionalFlow;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.poll.MessageQueueInfo;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
import com.google.domain.registry.model.poll.PollMessageExternalKeyConverter.PollMessageExternalKeyParseException;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
/**
|
||||
* An EPP flow for acknowledging poll messages.
|
||||
*
|
||||
* @error {@link PollAckFlow.InvalidMessageIdException}
|
||||
* @error {@link PollAckFlow.MessageDoesNotExistException}
|
||||
* @error {@link PollAckFlow.MissingMessageIdException}
|
||||
* @error {@link PollAckFlow.NotAuthorizedToAckMessageException}
|
||||
*/
|
||||
public class PollAckFlow extends PollFlow implements TransactionalFlow {
|
||||
|
||||
@Override
|
||||
public final EppOutput run() throws EppException {
|
||||
if (command.getMessageId() == null) {
|
||||
throw new MissingMessageIdException();
|
||||
}
|
||||
|
||||
Key<PollMessage> pollMessageKey;
|
||||
// Try parsing the messageId, and throw an exception if it's invalid.
|
||||
try {
|
||||
pollMessageKey = PollMessage.EXTERNAL_KEY_CONVERTER.reverse().convert(command.getMessageId());
|
||||
} catch (PollMessageExternalKeyParseException e) {
|
||||
throw new InvalidMessageIdException(command.getMessageId());
|
||||
}
|
||||
|
||||
// Load the message to be acked. If a message is queued to be delivered in the future, we treat
|
||||
// it as if it doesn't exist yet.
|
||||
PollMessage pollMessage = ofy().load().key(pollMessageKey).now();
|
||||
if (pollMessage == null || !isBeforeOrAt(pollMessage.getEventTime(), now)) {
|
||||
throw new MessageDoesNotExistException(command.getMessageId());
|
||||
}
|
||||
|
||||
// Make sure this client is authorized to ack this message. It could be that the message is
|
||||
// supposed to go to a different registrar.
|
||||
if (!getClientId().equals(pollMessage.getClientId())) {
|
||||
throw new NotAuthorizedToAckMessageException();
|
||||
}
|
||||
|
||||
// This keeps track of whether we should include the current acked message in the updated
|
||||
// message count that's returned to the user. The only case where we do so is if an autorenew
|
||||
// poll message is acked, but its next event is already ready to be delivered.
|
||||
boolean includeAckedMessageInCount = false;
|
||||
if (pollMessage instanceof PollMessage.OneTime) {
|
||||
// One-time poll messages are deleted once acked.
|
||||
ofy().delete().entity(pollMessage);
|
||||
} else {
|
||||
checkState(pollMessage instanceof PollMessage.Autorenew, "Unknown poll message type");
|
||||
PollMessage.Autorenew autorenewPollMessage = (PollMessage.Autorenew) pollMessage;
|
||||
|
||||
// Move the eventTime of this autorenew poll message forward by a year.
|
||||
DateTime nextEventTime = autorenewPollMessage.getEventTime().plusYears(1);
|
||||
|
||||
// If the next event falls within the bounds of the end time, then just update the eventTime
|
||||
// and re-save it for future autorenew poll messages to be delivered. Otherwise, this
|
||||
// autorenew poll message has no more events to deliver and should be deleted.
|
||||
if (nextEventTime.isBefore(autorenewPollMessage.getAutorenewEndTime())) {
|
||||
ofy().save().entity(autorenewPollMessage.asBuilder().setEventTime(nextEventTime).build());
|
||||
includeAckedMessageInCount = isBeforeOrAt(nextEventTime, now);
|
||||
} else {
|
||||
ofy().delete().entity(autorenewPollMessage);
|
||||
}
|
||||
}
|
||||
// We need to return the new queue length. If this was the last message in the queue being
|
||||
// acked, then we return a special status code indicating that. Note that the query will
|
||||
// include the message being acked.
|
||||
int messageCount = getMessageQueueLength();
|
||||
if (!includeAckedMessageInCount) {
|
||||
messageCount--;
|
||||
}
|
||||
if (messageCount <= 0) {
|
||||
return createOutput(SuccessWithNoMessages);
|
||||
}
|
||||
|
||||
return createOutput(
|
||||
Success,
|
||||
MessageQueueInfo.create(
|
||||
null, // eventTime
|
||||
null, // msg
|
||||
messageCount,
|
||||
command.getMessageId()),
|
||||
null, // responseData
|
||||
null); // extensions
|
||||
}
|
||||
|
||||
/** Registrar is not authorized to ack this message. */
|
||||
static class NotAuthorizedToAckMessageException extends AuthorizationErrorException {
|
||||
public NotAuthorizedToAckMessageException() {
|
||||
super("Registrar is not authorized to ack this message");
|
||||
}
|
||||
}
|
||||
|
||||
/** Message with this id does not exist. */
|
||||
public static class MessageDoesNotExistException extends ObjectDoesNotExistException {
|
||||
public MessageDoesNotExistException(String messageIdString) {
|
||||
super(PollMessage.class, messageIdString);
|
||||
}
|
||||
}
|
||||
|
||||
/** Message id is invalid. */
|
||||
static class InvalidMessageIdException extends ParameterValueSyntaxErrorException {
|
||||
public InvalidMessageIdException(String messageIdStr) {
|
||||
super(String.format("Message id \"%s\" is invalid", messageIdStr));
|
||||
}
|
||||
}
|
||||
|
||||
/** Message id is required. */
|
||||
static class MissingMessageIdException extends RequiredParameterMissingException {
|
||||
public MissingMessageIdException() {
|
||||
super("Message id is required");
|
||||
}
|
||||
}
|
||||
}
|
90
java/google/registry/flows/poll/PollFlow.java
Normal file
90
java/google/registry/flows/poll/PollFlow.java
Normal file
|
@ -0,0 +1,90 @@
|
|||
// 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.flows.poll;
|
||||
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.LoggedInFlow;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.Poll;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Response;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseData;
|
||||
import com.google.domain.registry.model.eppoutput.Response.ResponseExtension;
|
||||
import com.google.domain.registry.model.eppoutput.Result;
|
||||
import com.google.domain.registry.model.poll.MessageQueueInfo;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
import com.googlecode.objectify.Work;
|
||||
import com.googlecode.objectify.cmd.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Base class of EPP Poll command flows. Mostly provides datastore helper methods. */
|
||||
public abstract class PollFlow extends LoggedInFlow {
|
||||
|
||||
protected Poll command;
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected final void initLoggedInFlow() throws EppException {
|
||||
command = (Poll) eppInput.getCommandWrapper().getCommand();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a query for all poll messages for the logged in registrar in the current TLD which are
|
||||
* not in the future.
|
||||
*/
|
||||
private Query<PollMessage> getQuery() {
|
||||
return ofy().doTransactionless(new Work<Query<PollMessage>>() {
|
||||
@Override
|
||||
public Query<PollMessage> run() {
|
||||
return ofy().load()
|
||||
.type(PollMessage.class)
|
||||
.filter("clientId", getClientId())
|
||||
.filter("eventTime <=", now.toDate());
|
||||
}});
|
||||
}
|
||||
|
||||
/** Return the length of the message queue for the logged in registrar. */
|
||||
protected int getMessageQueueLength() {
|
||||
return getQuery().keys().list().size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Keys of all active PollMessage entities for the current client ordered by
|
||||
* eventTime.
|
||||
*/
|
||||
protected List<Key<PollMessage>> getMessageQueueKeysInOrder() {
|
||||
return getQuery().order("eventTime").keys().list();
|
||||
}
|
||||
|
||||
protected EppOutput createOutput(
|
||||
Result.Code code,
|
||||
MessageQueueInfo messageQueueInfo,
|
||||
ImmutableList<ResponseData> responseData,
|
||||
ImmutableList<ResponseExtension> responseExtensions) {
|
||||
return EppOutput.create(new Response.Builder()
|
||||
.setTrid(trid)
|
||||
.setResult(Result.create(code))
|
||||
.setMessageQueueInfo(messageQueueInfo)
|
||||
.setResData(responseData)
|
||||
.setExtensions(responseExtensions)
|
||||
.setExecutionTime(now)
|
||||
.build());
|
||||
}
|
||||
}
|
71
java/google/registry/flows/poll/PollRequestFlow.java
Normal file
71
java/google/registry/flows/poll/PollRequestFlow.java
Normal file
|
@ -0,0 +1,71 @@
|
|||
// 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.flows.poll;
|
||||
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.SuccessWithAckMessage;
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.SuccessWithNoMessages;
|
||||
import static com.google.domain.registry.model.ofy.ObjectifyService.ofy;
|
||||
import static com.google.domain.registry.util.CollectionUtils.forceEmptyToNull;
|
||||
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValueSyntaxErrorException;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.poll.MessageQueueInfo;
|
||||
import com.google.domain.registry.model.poll.PollMessage;
|
||||
|
||||
import com.googlecode.objectify.Key;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An EPP flow for requesting poll messages.
|
||||
*
|
||||
* @error {@link PollRequestFlow.UnexpectedMessageIdException}
|
||||
*/
|
||||
public class PollRequestFlow extends PollFlow {
|
||||
|
||||
@Override
|
||||
public final EppOutput run() throws EppException {
|
||||
if (command.getMessageId() != null) {
|
||||
throw new UnexpectedMessageIdException();
|
||||
}
|
||||
|
||||
List<Key<PollMessage>> pollMessageKeys = getMessageQueueKeysInOrder();
|
||||
// Retrieve the oldest message from the queue that still exists -- since the query is eventually
|
||||
// consistent, it may return keys to some entities that no longer exist.
|
||||
for (Key<PollMessage> key : pollMessageKeys) {
|
||||
PollMessage pollMessage = ofy().load().key(key).now();
|
||||
if (pollMessage != null) {
|
||||
return createOutput(
|
||||
SuccessWithAckMessage,
|
||||
MessageQueueInfo.create(
|
||||
pollMessage.getEventTime(),
|
||||
pollMessage.getMsg(),
|
||||
pollMessageKeys.size(),
|
||||
PollMessage.EXTERNAL_KEY_CONVERTER.convert(key)),
|
||||
forceEmptyToNull(pollMessage.getResponseData()),
|
||||
forceEmptyToNull(pollMessage.getResponseExtensions()));
|
||||
}
|
||||
}
|
||||
return createOutput(SuccessWithNoMessages);
|
||||
}
|
||||
|
||||
/** Unexpected message id. */
|
||||
static class UnexpectedMessageIdException extends ParameterValueSyntaxErrorException {
|
||||
public UnexpectedMessageIdException() {
|
||||
super("Unexpected message id");
|
||||
}
|
||||
}
|
||||
}
|
27
java/google/registry/flows/session/HelloFlow.java
Normal file
27
java/google/registry/flows/session/HelloFlow.java
Normal file
|
@ -0,0 +1,27 @@
|
|||
// 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.flows.session;
|
||||
|
||||
import com.google.domain.registry.flows.Flow;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Greeting;
|
||||
|
||||
/** A flow for an Epp "hello". */
|
||||
public class HelloFlow extends Flow {
|
||||
@Override
|
||||
public EppOutput run() {
|
||||
return EppOutput.create(new Greeting());
|
||||
}
|
||||
}
|
200
java/google/registry/flows/session/LoginFlow.java
Normal file
200
java/google/registry/flows/session/LoginFlow.java
Normal file
|
@ -0,0 +1,200 @@
|
|||
// 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.flows.session;
|
||||
|
||||
import static com.google.common.collect.Sets.difference;
|
||||
import static com.google.domain.registry.util.CollectionUtils.nullToEmpty;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.EppException.AuthenticationErrorClosingConnectionException;
|
||||
import com.google.domain.registry.flows.EppException.AuthenticationErrorException;
|
||||
import com.google.domain.registry.flows.EppException.AuthorizationErrorException;
|
||||
import com.google.domain.registry.flows.EppException.CommandUseErrorException;
|
||||
import com.google.domain.registry.flows.EppException.ParameterValuePolicyErrorException;
|
||||
import com.google.domain.registry.flows.EppException.UnimplementedExtensionException;
|
||||
import com.google.domain.registry.flows.EppException.UnimplementedObjectServiceException;
|
||||
import com.google.domain.registry.flows.EppException.UnimplementedOptionException;
|
||||
import com.google.domain.registry.flows.Flow;
|
||||
import com.google.domain.registry.flows.TransportCredentials;
|
||||
import com.google.domain.registry.model.eppcommon.ProtocolDefinition;
|
||||
import com.google.domain.registry.model.eppcommon.ProtocolDefinition.ServiceExtension;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.Login;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.Options;
|
||||
import com.google.domain.registry.model.eppinput.EppInput.Services;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
import com.google.domain.registry.model.eppoutput.Result.Code;
|
||||
import com.google.domain.registry.model.registrar.Registrar;
|
||||
import com.google.domain.registry.util.FormattingLogger;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* An EPP flow for login.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.EppConsoleServlet.GaeUserCredentials.BadGaeUserIdException}
|
||||
* @error {@link com.google.domain.registry.flows.EppConsoleServlet.GaeUserCredentials.UserNotLoggedInException}
|
||||
* @error {@link com.google.domain.registry.flows.EppException.UnimplementedExtensionException}
|
||||
* @error {@link com.google.domain.registry.flows.EppException.UnimplementedObjectServiceException}
|
||||
* @error {@link com.google.domain.registry.flows.EppException.UnimplementedProtocolVersionException}
|
||||
* @error {@link com.google.domain.registry.flows.TlsCredentials.BadRegistrarCertificateException}
|
||||
* @error {@link com.google.domain.registry.flows.TlsCredentials.BadRegistrarIpAddressException}
|
||||
* @error {@link com.google.domain.registry.flows.TlsCredentials.MissingRegistrarCertificateException}
|
||||
* @error {@link com.google.domain.registry.flows.TlsCredentials.NoSniException}
|
||||
* @error {@link LoginFlow.AlreadyLoggedInException}
|
||||
* @error {@link LoginFlow.BadRegistrarClientIdException}
|
||||
* @error {@link LoginFlow.BadRegistrarPasswordException}
|
||||
* @error {@link LoginFlow.TooManyFailedLoginsException}
|
||||
* @error {@link LoginFlow.PasswordChangesNotSupportedException}
|
||||
* @error {@link LoginFlow.RegistrarAccountNotActiveException}
|
||||
* @error {@link LoginFlow.UnsupportedLanguageException}
|
||||
*/
|
||||
public class LoginFlow extends Flow {
|
||||
|
||||
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
|
||||
|
||||
/** This is the IANA ID used for the internal account of the registry. */
|
||||
private static final long INTERNAL_IANA_REGISTRAR_ID = 9999L;
|
||||
|
||||
/** Maximum number of failed login attempts allowed per connection. */
|
||||
private static final int MAX_FAILED_LOGIN_ATTEMPTS_PER_CONNECTION = 3;
|
||||
|
||||
/** Run the flow and log errors. */
|
||||
@Override
|
||||
public final EppOutput run() throws EppException {
|
||||
try {
|
||||
return runWithoutLogging();
|
||||
} catch (EppException e) {
|
||||
logger.warning("Login failed: " + e.getMessage());
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/** Run the flow without bothering to log errors. The {@link #run} method will do that for us. */
|
||||
public final EppOutput runWithoutLogging() throws EppException {
|
||||
Login login = (Login) eppInput.getCommandWrapper().getCommand();
|
||||
if (getClientId() != null) {
|
||||
throw new AlreadyLoggedInException();
|
||||
}
|
||||
Options options = login.getOptions();
|
||||
if (!ProtocolDefinition.LANGUAGE.equals(options.getLanguage())) {
|
||||
throw new UnsupportedLanguageException();
|
||||
}
|
||||
Services services = login.getServices();
|
||||
Set<String> unsupportedObjectServices = difference(
|
||||
nullToEmpty(services.getObjectServices()),
|
||||
ProtocolDefinition.SUPPORTED_OBJECT_SERVICES);
|
||||
if (!unsupportedObjectServices.isEmpty()) {
|
||||
throw new UnimplementedObjectServiceException();
|
||||
}
|
||||
ImmutableSet.Builder<String> serviceExtensionUrisBuilder = new ImmutableSet.Builder<>();
|
||||
for (String uri : nullToEmpty(services.getServiceExtensions())) {
|
||||
ServiceExtension serviceExtension = ProtocolDefinition.getServiceExtensionFromUri(uri);
|
||||
if (serviceExtension == null) {
|
||||
throw new UnimplementedExtensionException();
|
||||
}
|
||||
serviceExtensionUrisBuilder.add(uri);
|
||||
}
|
||||
Registrar registrar = Registrar.loadByClientId(login.getClientId());
|
||||
if (registrar == null) {
|
||||
throw new BadRegistrarClientIdException(login.getClientId());
|
||||
}
|
||||
|
||||
TransportCredentials credentials = sessionMetadata.getTransportCredentials();
|
||||
// AuthenticationErrorExceptions will propagate up through here.
|
||||
if (credentials != null) { // Allow no-credential logins, for load-testing and RDE.
|
||||
try {
|
||||
credentials.validate(registrar);
|
||||
} catch (AuthenticationErrorException e) {
|
||||
sessionMetadata.incrementFailedLoginAttempts();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
final boolean requiresLoginCheck = credentials == null || !credentials.performsLoginCheck();
|
||||
if (requiresLoginCheck && !registrar.testPassword(login.getPassword())) {
|
||||
sessionMetadata.incrementFailedLoginAttempts();
|
||||
if (sessionMetadata.getFailedLoginAttempts() > MAX_FAILED_LOGIN_ATTEMPTS_PER_CONNECTION) {
|
||||
throw new TooManyFailedLoginsException();
|
||||
} else {
|
||||
throw new BadRegistrarPasswordException();
|
||||
}
|
||||
}
|
||||
if (registrar.getState().equals(Registrar.State.PENDING)) {
|
||||
throw new RegistrarAccountNotActiveException();
|
||||
}
|
||||
if (login.getNewPassword() != null) { // We don't support in-band password changes.
|
||||
throw new PasswordChangesNotSupportedException();
|
||||
}
|
||||
|
||||
// We are in!
|
||||
sessionMetadata.resetFailedLoginAttempts();
|
||||
sessionMetadata.setClientId(login.getClientId());
|
||||
sessionMetadata.setSuperuser(
|
||||
Objects.equals(INTERNAL_IANA_REGISTRAR_ID, registrar.getIanaIdentifier()));
|
||||
sessionMetadata.setServiceExtensionUris(serviceExtensionUrisBuilder.build());
|
||||
return createOutput(Code.Success);
|
||||
}
|
||||
|
||||
/** Registrar with this client ID could not be found. */
|
||||
static class BadRegistrarClientIdException extends AuthenticationErrorException {
|
||||
public BadRegistrarClientIdException(String clientId) {
|
||||
super("Registrar with this client ID could not be found: " + clientId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrar password is incorrect. */
|
||||
static class BadRegistrarPasswordException extends AuthenticationErrorException {
|
||||
public BadRegistrarPasswordException() {
|
||||
super("Registrar password is incorrect");
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrar login failed too many times. */
|
||||
static class TooManyFailedLoginsException extends AuthenticationErrorClosingConnectionException {
|
||||
public TooManyFailedLoginsException() {
|
||||
super("Registrar login failed too many times");
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrar account is not active. */
|
||||
static class RegistrarAccountNotActiveException extends AuthorizationErrorException {
|
||||
public RegistrarAccountNotActiveException() {
|
||||
super("Registrar account is not active");
|
||||
}
|
||||
}
|
||||
|
||||
/** Registrar is already logged in. */
|
||||
static class AlreadyLoggedInException extends CommandUseErrorException {
|
||||
public AlreadyLoggedInException() {
|
||||
super("Registrar is already logged in");
|
||||
}
|
||||
}
|
||||
|
||||
/** Specified language is not supported. */
|
||||
static class UnsupportedLanguageException extends ParameterValuePolicyErrorException {
|
||||
public UnsupportedLanguageException() {
|
||||
super("Specified language is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
/** In-band password changes are not supported. */
|
||||
static class PasswordChangesNotSupportedException extends UnimplementedOptionException {
|
||||
public PasswordChangesNotSupportedException() {
|
||||
super("In-band password changes are not supported");
|
||||
}
|
||||
}
|
||||
}
|
34
java/google/registry/flows/session/LogoutFlow.java
Normal file
34
java/google/registry/flows/session/LogoutFlow.java
Normal 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.flows.session;
|
||||
|
||||
import static com.google.domain.registry.model.eppoutput.Result.Code.SuccessAndClose;
|
||||
|
||||
import com.google.domain.registry.flows.EppException;
|
||||
import com.google.domain.registry.flows.LoggedInFlow;
|
||||
import com.google.domain.registry.model.eppoutput.EppOutput;
|
||||
|
||||
/**
|
||||
* An EPP flow for logout.
|
||||
*
|
||||
* @error {@link com.google.domain.registry.flows.LoggedInFlow.NotLoggedInException}
|
||||
*/
|
||||
public class LogoutFlow extends LoggedInFlow {
|
||||
@Override
|
||||
public final EppOutput run() throws EppException {
|
||||
sessionMetadata.invalidate();
|
||||
return createOutput(SuccessAndClose);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue