// 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. * *

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 UNDECLARED_URIS_BLACKLIST = FEE_EXTENSION_URIS; private final ImmutableSet.Builder> implementedBuilder = new ImmutableSet.Builder<>(); @Inject EppInput eppInput; @Inject SessionMetadata sessionMetadata; @Inject @ClientId String clientId; @Inject @Superuser boolean isSuperuser; @Inject Class flowClass; @Inject EppRequestSource eppRequestSource; @Inject ExtensionManager() {} @SafeVarargs public final void register(Class... extension) { implementedBuilder.add(extension); } public void validate() throws EppException { ImmutableSet.Builder> suppliedBuilder = new ImmutableSet.Builder<>(); for (CommandExtension extension : eppInput.getCommandWrapper().getExtensions()) { suppliedBuilder.add(extension.getClass()); } ImmutableSet> suppliedExtensions = suppliedBuilder.build(); ImmutableSet> implementedExtensions = implementedBuilder.build(); ImmutableList suppliedExtensionInstances = eppInput.getCommandWrapper().getExtensions(); checkForUndeclaredExtensions(suppliedExtensions); checkForRestrictedExtensions(suppliedExtensions); checkForDuplicateExtensions(suppliedExtensionInstances, suppliedExtensions); checkForUnimplementedExtensions(suppliedExtensionInstances, implementedExtensions); } private void checkForUndeclaredExtensions( ImmutableSet> suppliedExtensions) throws UndeclaredServiceExtensionException { ImmutableSet.Builder suppliedUris = new ImmutableSet.Builder<>(); for (Class extension : suppliedExtensions) { suppliedUris.add(getCommandExtensionUri(extension)); } Set declaredUris = sessionMetadata.getServiceExtensionUris(); Set undeclaredUris = difference(suppliedUris.build(), declaredUris); if (undeclaredUris.isEmpty()) { return; } Set 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> 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 suppliedExtension : suppliedExtensions) { if (SuperuserExtension.class.isAssignableFrom(suppliedExtension) && (!eppRequestSource.equals(EppRequestSource.TOOL) || !isSuperuser)) { throw new UnauthorizedForSuperuserExtensionException(); } } } private static void checkForDuplicateExtensions( ImmutableList suppliedExtensionInstances, ImmutableSet> implementedExtensions) throws UnsupportedRepeatedExtensionException { for (Class implemented : implementedExtensions) { if (FluentIterable.from(suppliedExtensionInstances).filter(implemented).size() > 1) { throw new UnsupportedRepeatedExtensionException(); } } } private static void checkForUnimplementedExtensions( ImmutableList suppliedExtensionInstances, ImmutableSet> implementedExtensionClasses) throws UnimplementedExtensionException { ImmutableSet.Builder> unimplementedExtensionsBuilder = new ImmutableSet.Builder<>(); for (final CommandExtension instance : suppliedExtensionInstances) { if (!any( implementedExtensionClasses, new Predicate>() { @Override public boolean apply(Class implementedExtensionClass) { return implementedExtensionClass.isInstance(instance); }})) { unimplementedExtensionsBuilder.add(instance.getClass()); } } ImmutableSet> 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 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"); } } }