// 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 google.registry.flows; import static google.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 google.registry.flows.EppException.SyntaxErrorException; import google.registry.flows.EppException.UnimplementedCommandException; import google.registry.flows.contact.ContactCheckFlow; import google.registry.flows.contact.ContactCreateFlow; import google.registry.flows.contact.ContactDeleteFlow; import google.registry.flows.contact.ContactInfoFlow; import google.registry.flows.contact.ContactTransferApproveFlow; import google.registry.flows.contact.ContactTransferCancelFlow; import google.registry.flows.contact.ContactTransferQueryFlow; import google.registry.flows.contact.ContactTransferRejectFlow; import google.registry.flows.contact.ContactTransferRequestFlow; import google.registry.flows.contact.ContactUpdateFlow; import google.registry.flows.domain.ClaimsCheckFlow; import google.registry.flows.domain.DomainAllocateFlow; import google.registry.flows.domain.DomainApplicationCreateFlow; import google.registry.flows.domain.DomainApplicationDeleteFlow; import google.registry.flows.domain.DomainApplicationInfoFlow; import google.registry.flows.domain.DomainApplicationUpdateFlow; import google.registry.flows.domain.DomainCheckFlow; import google.registry.flows.domain.DomainCreateFlow; import google.registry.flows.domain.DomainDeleteFlow; import google.registry.flows.domain.DomainInfoFlow; import google.registry.flows.domain.DomainRenewFlow; import google.registry.flows.domain.DomainRestoreRequestFlow; import google.registry.flows.domain.DomainTransferApproveFlow; import google.registry.flows.domain.DomainTransferCancelFlow; import google.registry.flows.domain.DomainTransferQueryFlow; import google.registry.flows.domain.DomainTransferRejectFlow; import google.registry.flows.domain.DomainTransferRequestFlow; import google.registry.flows.domain.DomainUpdateFlow; import google.registry.flows.host.HostCheckFlow; import google.registry.flows.host.HostCreateFlow; import google.registry.flows.host.HostDeleteFlow; import google.registry.flows.host.HostInfoFlow; import google.registry.flows.host.HostUpdateFlow; import google.registry.flows.poll.PollAckFlow; import google.registry.flows.poll.PollRequestFlow; import google.registry.flows.session.HelloFlow; import google.registry.flows.session.LoginFlow; import google.registry.flows.session.LogoutFlow; import google.registry.model.contact.ContactCommand; import google.registry.model.domain.DomainCommand; import google.registry.model.domain.allocate.AllocateCreateExtension; import google.registry.model.domain.launch.ApplicationIdTargetExtension; import google.registry.model.domain.launch.LaunchCheckExtension; import google.registry.model.domain.launch.LaunchCheckExtension.CheckType; import google.registry.model.domain.launch.LaunchCreateExtension; import google.registry.model.domain.launch.LaunchPhase; import google.registry.model.domain.rgp.RestoreCommand.RestoreOp; import google.registry.model.domain.rgp.RgpUpdateExtension; import google.registry.model.eppinput.EppInput; import google.registry.model.eppinput.EppInput.Hello; import google.registry.model.eppinput.EppInput.InnerCommand; import google.registry.model.eppinput.EppInput.Login; import google.registry.model.eppinput.EppInput.Logout; import google.registry.model.eppinput.EppInput.Poll; import google.registry.model.eppinput.EppInput.ResourceCommandWrapper; import google.registry.model.eppinput.EppInput.Transfer; import google.registry.model.eppinput.EppInput.Transfer.TransferOp; import google.registry.model.eppinput.ResourceCommand; import google.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 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 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 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> commandFlows = ImmutableMap., Class>of( Login.class, LoginFlow.class, Logout.class, LogoutFlow.class); @Override Class 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 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 }, but logically a totally * separate flow. * *

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 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 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> resourceCrudFlows = new ImmutableMap.Builder, Class>() .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 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 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> applicationFlows = ImmutableMap., Class>of( DomainCommand.Create.class, DomainApplicationCreateFlow.class, DomainCommand.Delete.class, DomainApplicationDeleteFlow.class, DomainCommand.Info.class, DomainApplicationInfoFlow.class, DomainCommand.Update.class, DomainApplicationUpdateFlow.class); private final Set launchPhases = ImmutableSet.of( LaunchPhase.SUNRISE, LaunchPhase.SUNRUSH, LaunchPhase.LANDRUSH); @Override Class 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, TransferOp, Class> transferFlows = ImmutableTable ., TransferOp, Class>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 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 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 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"); } } }