google-nomulus/java/google/registry/flows/ExtensionManager.java
bbilbo 2e4b63bb79 Add support for a domain transfer request superuser EPP extension
Allow superusers to change the transfer period to zero years and allow
superusers to change the automatic transfer length.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=167598314
2017-09-12 15:51:50 -04:00

178 lines
8 KiB
Java

// Copyright 2017 The Nomulus Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package google.registry.flows;
import static com.google.common.collect.Iterables.any;
import static com.google.common.collect.Sets.difference;
import static com.google.common.collect.Sets.intersection;
import static google.registry.model.domain.fee.Fee.FEE_EXTENSION_URIS;
import static google.registry.model.eppcommon.ProtocolDefinition.ServiceExtension.getCommandExtensionUri;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import google.registry.flows.EppException.CommandUseErrorException;
import google.registry.flows.EppException.SyntaxErrorException;
import google.registry.flows.EppException.UnimplementedExtensionException;
import google.registry.flows.FlowModule.ClientId;
import google.registry.flows.FlowModule.Superuser;
import google.registry.flows.exceptions.OnlyToolCanPassMetadataException;
import google.registry.flows.exceptions.UnauthorizedForSuperuserExtensionException;
import google.registry.model.domain.metadata.MetadataExtension;
import google.registry.model.domain.superuser.SuperuserExtension;
import google.registry.model.eppinput.EppInput;
import google.registry.model.eppinput.EppInput.CommandExtension;
import google.registry.util.FormattingLogger;
import java.util.Set;
import javax.inject.Inject;
/**
* Helper to validate extensions on an EPP command.
*
* <p>This class checks that the declared extension URIs in an EPP request as well as the actually
* supplied extensions in the XML are compatible with the extensions supported by a flow.
*/
public final class ExtensionManager {
private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass();
/** Blacklist of extension URIs that cause an error if they are used without being declared. */
private static final ImmutableSet<String> UNDECLARED_URIS_BLACKLIST = FEE_EXTENSION_URIS;
private final ImmutableSet.Builder<Class<? extends CommandExtension>> implementedBuilder =
new ImmutableSet.Builder<>();
@Inject EppInput eppInput;
@Inject SessionMetadata sessionMetadata;
@Inject @ClientId String clientId;
@Inject @Superuser boolean isSuperuser;
@Inject Class<? extends Flow> flowClass;
@Inject EppRequestSource eppRequestSource;
@Inject ExtensionManager() {}
@SafeVarargs
public final void register(Class<? extends CommandExtension>... extension) {
implementedBuilder.add(extension);
}
public void validate() throws EppException {
ImmutableSet.Builder<Class<? extends CommandExtension>> suppliedBuilder =
new ImmutableSet.Builder<>();
for (CommandExtension extension : eppInput.getCommandWrapper().getExtensions()) {
suppliedBuilder.add(extension.getClass());
}
ImmutableSet<Class<? extends CommandExtension>> suppliedExtensions = suppliedBuilder.build();
ImmutableSet<Class<? extends CommandExtension>> implementedExtensions =
implementedBuilder.build();
ImmutableList<CommandExtension> suppliedExtensionInstances =
eppInput.getCommandWrapper().getExtensions();
checkForUndeclaredExtensions(suppliedExtensions);
checkForRestrictedExtensions(suppliedExtensions);
checkForDuplicateExtensions(suppliedExtensionInstances, suppliedExtensions);
checkForUnimplementedExtensions(suppliedExtensionInstances, implementedExtensions);
}
private void checkForUndeclaredExtensions(
ImmutableSet<Class<? extends CommandExtension>> suppliedExtensions)
throws UndeclaredServiceExtensionException {
ImmutableSet.Builder<String> suppliedUris = new ImmutableSet.Builder<>();
for (Class<? extends CommandExtension> extension : suppliedExtensions) {
suppliedUris.add(getCommandExtensionUri(extension));
}
Set<String> declaredUris = sessionMetadata.getServiceExtensionUris();
Set<String> undeclaredUris = difference(suppliedUris.build(), declaredUris);
if (undeclaredUris.isEmpty()) {
return;
}
Set<String> undeclaredUrisThatError = intersection(undeclaredUris, UNDECLARED_URIS_BLACKLIST);
if (!undeclaredUrisThatError.isEmpty()) {
throw new UndeclaredServiceExtensionException(undeclaredUrisThatError);
}
logger.infofmt(
"Client %s is attempting to run %s without declaring URIs %s on login",
clientId,
flowClass.getSimpleName(),
undeclaredUris);
}
private void checkForRestrictedExtensions(
ImmutableSet<Class<? extends CommandExtension>> suppliedExtensions)
throws OnlyToolCanPassMetadataException, UnauthorizedForSuperuserExtensionException {
if (suppliedExtensions.contains(MetadataExtension.class)
&& !eppRequestSource.equals(EppRequestSource.TOOL)) {
throw new OnlyToolCanPassMetadataException();
}
// Can't use suppliedExtension.contains() here because the SuperuserExtension has child classes.
for (Class<? extends CommandExtension> suppliedExtension : suppliedExtensions) {
if (SuperuserExtension.class.isAssignableFrom(suppliedExtension)
&& (!eppRequestSource.equals(EppRequestSource.TOOL) || !isSuperuser)) {
throw new UnauthorizedForSuperuserExtensionException();
}
}
}
private static void checkForDuplicateExtensions(
ImmutableList<CommandExtension> suppliedExtensionInstances,
ImmutableSet<Class<? extends CommandExtension>> implementedExtensions)
throws UnsupportedRepeatedExtensionException {
for (Class<? extends CommandExtension> implemented : implementedExtensions) {
if (FluentIterable.from(suppliedExtensionInstances).filter(implemented).size() > 1) {
throw new UnsupportedRepeatedExtensionException();
}
}
}
private static void checkForUnimplementedExtensions(
ImmutableList<CommandExtension> suppliedExtensionInstances,
ImmutableSet<Class<? extends CommandExtension>> implementedExtensionClasses)
throws UnimplementedExtensionException {
ImmutableSet.Builder<Class<? extends CommandExtension>> unimplementedExtensionsBuilder =
new ImmutableSet.Builder<>();
for (final CommandExtension instance : suppliedExtensionInstances) {
if (!any(
implementedExtensionClasses,
new Predicate<Class<? extends CommandExtension>>() {
@Override
public boolean apply(Class<? extends CommandExtension> implementedExtensionClass) {
return implementedExtensionClass.isInstance(instance);
}})) {
unimplementedExtensionsBuilder.add(instance.getClass());
}
}
ImmutableSet<Class<? extends CommandExtension>> unimplementedExtensions =
unimplementedExtensionsBuilder.build();
if (!unimplementedExtensions.isEmpty()) {
logger.infofmt("Unimplemented extensions: %s", unimplementedExtensions);
throw new UnimplementedExtensionException();
}
}
/** 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)));
}
}
/** Unsupported repetition of an extension. */
static class UnsupportedRepeatedExtensionException extends SyntaxErrorException {
public UnsupportedRepeatedExtensionException() {
super("Unsupported repetition of an extension");
}
}
}