/************************************************************************* * Copyright 2009-2015 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. ************************************************************************/ package com.eucalyptus.ws.server; import static com.eucalyptus.auth.principal.TemporaryAccessKey.TemporaryKeyType; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import java.util.Set; import com.eucalyptus.ws.Handlers; import com.eucalyptus.ws.handlers.MessageStackHandler; import org.apache.commons.codec.DecoderException; import org.apache.commons.codec.net.URLCodec; import org.jboss.netty.channel.ChannelPipeline; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.handler.codec.http.HttpHeaders; import org.jboss.netty.handler.codec.http.HttpMethod; import org.jboss.netty.handler.codec.http.HttpRequest; import com.eucalyptus.http.MappingHttpRequest; import com.eucalyptus.util.Strings; import com.eucalyptus.ws.stages.HmacUserAuthenticationStage; import com.eucalyptus.ws.stages.UnrollableStage; import com.eucalyptus.ws.util.HmacUtils.SignatureVersion; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Iterables; import com.google.common.collect.Sets; /** * */ public abstract class QueryPipeline extends FilteredPipeline { private final HmacUserAuthenticationStage auth; private final String name; private final Set<String> servicePathPrefixes; protected QueryPipeline( final String name, final String servicePathPrefix, final Set<TemporaryKeyType> allowedTemporaryCredentials ) { this( name, ImmutableSet.of( servicePathPrefix ), allowedTemporaryCredentials ); } protected QueryPipeline( final String name, final String servicePathPrefix, final Set<TemporaryKeyType> allowedTemporaryCredentials, final Set<SignatureVersion> allowedSignatureVersions ) { this( name, ImmutableSet.of( servicePathPrefix ), allowedTemporaryCredentials, allowedSignatureVersions ); } protected QueryPipeline( final String name, final Set<String> servicePathPrefixes, final Set<TemporaryKeyType> allowedTemporaryCredentials ) { this( name, servicePathPrefixes, allowedTemporaryCredentials, EnumSet.allOf( SignatureVersion.class ) ); } protected QueryPipeline( final String name, final Set<String> servicePathPrefixes, final Set<TemporaryKeyType> allowedTemporaryCredentials, final Set<SignatureVersion> allowedSignatureVersions ) { this.auth = new HmacUserAuthenticationStage( allowedTemporaryCredentials, allowedSignatureVersions ); this.name = name; this.servicePathPrefixes = ImmutableSet.copyOf( servicePathPrefixes ); } protected UnrollableStage getAuthenticationStage( ) { return auth; } @Override public ChannelPipeline addHandlers( final ChannelPipeline pipeline ) { pipeline.addLast( "aggregator", Handlers.newQueryHttpChunkAggregator()); pipeline.addLast( "parse-query-parameters",new MessageStackHandler( ){ @Override public void incomingMessage( final MessageEvent event ) throws Exception { if ( event.getMessage( ) instanceof MappingHttpRequest ) { final MappingHttpRequest httpRequest = ( MappingHttpRequest ) event.getMessage( ); if ( httpRequest.getMethod( ).equals( HttpMethod.POST ) ) { final Map<String, String> parameters = new HashMap<String, String>( httpRequest.getParameters( ) ); final Set<String> nonQueryParameters = Sets.newHashSet( ); final String query = httpRequest.getContentAsString( true ); for ( String p : query.split( "&" ) ) { String[] splitParam = p.split( "=", 2 ); String lhs = splitParam[ 0 ]; String rhs = splitParam.length == 2 ? splitParam[ 1 ] : null; try { if ( lhs != null ) lhs = new URLCodec( ).decode( lhs ); } catch ( DecoderException e ) { } try { if ( rhs != null ) rhs = new URLCodec( ).decode( rhs ); } catch ( DecoderException e ) { } parameters.put( lhs, rhs ); nonQueryParameters.add( lhs ); } httpRequest.getParameters( ).putAll( parameters ); httpRequest.addNonQueryParameterKeys( nonQueryParameters ); } } } } ); getAuthenticationStage( ).unrollStage( pipeline ); return null; } @Override public boolean checkAccepts( final HttpRequest message ) { if ( message instanceof MappingHttpRequest && !message.getHeaderNames().contains( "SOAPAction" )) { final boolean usesServicePath = Iterables.any( servicePathPrefixes, Strings.isPrefixOf( message.getUri( ) ) ); final boolean pathValidForService = validPathForService( message.getUri( ) ); final boolean validAuthForPipeline = validAuthForPipeline( (MappingHttpRequest)message ); if ( !validAuthForPipeline || ( !usesServicePath && !( pathValidForService && resolvesByHost( message.getHeader( HttpHeaders.Names.HOST ) ) ) ) ) { return false; } return true; } else { return false; } } @Override public String getName( ) { return name; } /** * Is the non-prefixed path one handled by this service. * * Non-prefixed paths that the service recognises are handled as long as the * host is also valid. * * @param path The non-prefixed path to check. * @return True if this this path is valid for the service */ protected boolean validPathForService( final String path ) { return path.isEmpty( ) || path.equals( "/" ) || path.startsWith( "/?" ); } /** * Does the given request use a valid authentication method for this pipeline. */ protected boolean validAuthForPipeline( final MappingHttpRequest request ) { return true; } }