com.eucalyptus.ws.handlers.WalrusAuthenticationHandler.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.ws.handlers.WalrusAuthenticationHandler.java

Source

/*************************************************************************
 * Copyright 2009-2012 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.
 *
 * This file may incorporate work covered under the following copyright
 * and permission notice:
 *
 *   Software License Agreement (BSD License)
 *
 *   Copyright (c) 2008, Regents of the University of California
 *   All rights reserved.
 *
 *   Redistribution and use of this software in source and binary forms,
 *   with or without modification, are permitted provided that the
 *   following conditions are met:
 *
 *     Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *     Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer
 *     in the documentation and/or other materials provided with the
 *     distribution.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 *   FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 *   COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *   INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 *   BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 *   LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 *   CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 *   LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 *   ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *   POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE
 *   THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL,
 *   COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE,
 *   AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING
 *   IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA,
 *   SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY,
 *   WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION,
 *   REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO
 *   IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT
 *   NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS.
 ************************************************************************/

package com.eucalyptus.ws.handlers;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.Arrays;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.log4j.Logger;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelPipelineCoverage;
import org.jboss.netty.channel.DownstreamMessageEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import com.eucalyptus.auth.login.AuthenticationException;
import com.eucalyptus.auth.login.SecurityContext;
import com.eucalyptus.auth.login.WalrusWrappedComponentCredentials;
import com.eucalyptus.auth.login.WalrusWrappedCredentials;
import com.eucalyptus.auth.principal.Principals;
import com.eucalyptus.context.Context;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.context.NoSuchContextException;
import com.eucalyptus.http.MappingHttpRequest;
import com.eucalyptus.util.StorageProperties;
import com.eucalyptus.util.WalrusProperties;
import com.eucalyptus.util.WalrusUtil;

@ChannelPipelineCoverage("one")
public class WalrusAuthenticationHandler extends MessageStackHandler {
    private static Logger LOG = Logger.getLogger(WalrusAuthenticationHandler.class);

    public enum SecurityParameter {
        AWSAccessKeyId, Timestamp, Expires, Signature, Authorization, Date, Content_MD5, Content_Type
    }

    @Override
    public void incomingMessage(ChannelHandlerContext ctx, MessageEvent event) throws Exception {
        if (event.getMessage() instanceof MappingHttpRequest) {
            MappingHttpRequest httpRequest = (MappingHttpRequest) event.getMessage();
            if (httpRequest.containsHeader(WalrusProperties.Headers.S3UploadPolicy.toString())) {
                checkUploadPolicy(httpRequest);
            }
            handle(httpRequest);
        }
    }

    public void handle(MappingHttpRequest httpRequest) throws AuthenticationException {
        Map<String, String> parameters = httpRequest.getParameters();
        String verb = httpRequest.getMethod().getName();
        String addr = httpRequest.getUri();

        if (httpRequest.containsHeader(StorageProperties.StorageParameters.EucaSignature.toString())) {
            //possible internal request -- perform authentication using internal credentials
            String date = httpRequest.getAndRemoveHeader(SecurityParameter.Date.toString());
            String signature = httpRequest
                    .getAndRemoveHeader(StorageProperties.StorageParameters.EucaSignature.toString());
            String certString = null;
            if (httpRequest.containsHeader(StorageProperties.StorageParameters.EucaCert.toString())) {
                certString = httpRequest
                        .getAndRemoveHeader(StorageProperties.StorageParameters.EucaCert.toString());
            }
            String data = verb + "\n" + date + "\n" + addr + "\n";
            String effectiveUserID = httpRequest
                    .getAndRemoveHeader(StorageProperties.StorageParameters.EucaEffectiveUserId.toString());
            try {
                SecurityContext
                        .getLoginContext(new WalrusWrappedComponentCredentials(httpRequest.getCorrelationId(), data,
                                effectiveUserID, signature, certString))
                        .login();
            } catch (Exception ex) {
                LOG.error(ex);
                throw new AuthenticationException(ex);
            }
        } else {
            //external user request
            String content_md5 = httpRequest.getHeader("Content-MD5");
            content_md5 = content_md5 == null ? "" : content_md5;
            String content_type = httpRequest.getHeader(WalrusProperties.CONTENT_TYPE);
            content_type = content_type == null ? "" : content_type;

            String targetHost = httpRequest.getHeader(HttpHeaders.Names.HOST);
            if (targetHost.contains(".walrus")) {
                String bucket = targetHost.substring(0, targetHost.indexOf(".walrus"));
                addr = "/" + bucket + addr;
            }
            String[] addrStrings = addr.split("\\?");
            String addrString = addrStrings[0];

            if (addrStrings.length > 1) {
                //Split into individual parameter=value strings
                String[] params = addrStrings[1].split("&");

                //Sort the query parameters before adding them to the canonical string
                Arrays.sort(params);
                String[] pair = null;
                boolean first = true;
                try {
                    for (String qparam : params) {
                        pair = qparam.split("="); //pair[0] = param name, pair[1] = param value if it is present

                        for (WalrusProperties.SubResource subResource : WalrusProperties.SubResource.values()) {
                            if (pair[0].equals(subResource.toString())) {
                                if (first) {
                                    addrString += "?";
                                    first = false;
                                } else {
                                    addrString += "&";
                                }
                                addrString += subResource.toString()
                                        + (pair.length > 1 ? "=" + WalrusUtil.URLdecode(pair[1]) : "");
                            }
                        }
                    }
                } catch (UnsupportedEncodingException e) {
                    throw new AuthenticationException(
                            "Could not verify request. Failed url decoding query parameters: " + e.getMessage());
                }
            }

            if (httpRequest.containsHeader(SecurityParameter.Authorization.toString())) {
                String date;
                String verifyDate;
                if (httpRequest.containsHeader("x-amz-date")) {
                    date = "";
                    verifyDate = httpRequest.getHeader("x-amz-date");
                } else {
                    date = httpRequest.getAndRemoveHeader(SecurityParameter.Date.toString());
                    verifyDate = date;
                    if (date == null || date.length() <= 0)
                        throw new AuthenticationException("User authentication failed. Date must be specified.");
                }

                try {
                    Date dateToVerify = DateUtil.parseDate(verifyDate);
                    Date currentDate = new Date();
                    if (Math.abs(
                            currentDate.getTime() - dateToVerify.getTime()) > WalrusProperties.EXPIRATION_LIMIT)
                        throw new AuthenticationException("Message expired. Sorry.");
                } catch (Exception ex) {
                    throw new AuthenticationException("Unable to parse date.");
                }
                String data = verb + "\n" + content_md5 + "\n" + content_type + "\n" + date + "\n"
                        + getCanonicalizedAmzHeaders(httpRequest) + addrString;
                String authPart = httpRequest.getAndRemoveHeader(SecurityParameter.Authorization.toString());
                String sigString[] = getSigInfo(authPart);
                if (sigString.length < 2) {
                    throw new AuthenticationException("Invalid authentication header");
                }
                String accessKeyId = sigString[0];
                String signature = sigString[1];

                try {
                    SecurityContext.getLoginContext(new WalrusWrappedCredentials(httpRequest.getCorrelationId(),
                            data, accessKeyId, signature)).login();
                } catch (Exception ex) {
                    LOG.error(ex);
                    throw new AuthenticationException(ex);
                }
            } else if (parameters.containsKey(SecurityParameter.AWSAccessKeyId.toString())) {
                //query string authentication
                String accesskeyid = parameters.remove(SecurityParameter.AWSAccessKeyId.toString());
                try {
                    String signature = WalrusUtil
                            .URLdecode(parameters.remove(SecurityParameter.Signature.toString()));
                    if (signature == null) {
                        throw new AuthenticationException("User authentication failed. Null signature.");
                    }
                    String expires = parameters.remove(SecurityParameter.Expires.toString());
                    if (expires == null) {
                        throw new AuthenticationException("Authentication failed. Expires must be specified.");
                    }
                    if (checkExpires(expires)) {
                        String stringToSign = verb + "\n" + content_md5 + "\n" + content_type + "\n"
                                + Long.parseLong(expires) + "\n" + getCanonicalizedAmzHeaders(httpRequest)
                                + addrString;
                        try {
                            SecurityContext
                                    .getLoginContext(new WalrusWrappedCredentials(httpRequest.getCorrelationId(),
                                            stringToSign, accesskeyid, signature))
                                    .login();
                        } catch (Exception ex) {
                            LOG.error(ex);
                            throw new AuthenticationException(ex);
                        }
                    } else {
                        throw new AuthenticationException("Cannot process request. Expired.");
                    }
                } catch (Exception ex) {
                    throw new AuthenticationException("Could not verify request " + ex.getMessage());
                }
            } else {
                //anonymous request              
                try {
                    Context ctx = Contexts.lookup(httpRequest.getCorrelationId());
                    ctx.setUser(Principals.nobodyUser());
                } catch (NoSuchContextException e) {
                    LOG.error(e, e);
                    throw new AuthenticationException(e);
                }
            }
        }
    }

    private boolean checkExpires(String expires) {
        Long expireTime = Long.parseLong(expires);
        Long currentTime = new Date().getTime() / 1000;
        if (currentTime > expireTime)
            return false;
        return true;
    }

    private String[] getSigInfo(String auth_part) {
        int index = auth_part.lastIndexOf(" ");
        String sigString = auth_part.substring(index + 1);
        return sigString.split(":");
    }

    private String getCanonicalizedAmzHeaders(MappingHttpRequest httpRequest) {
        String result = "";
        Set<String> headerNames = httpRequest.getHeaderNames();

        TreeMap amzHeaders = new TreeMap<String, String>();
        for (String headerName : headerNames) {
            String headerNameString = headerName.toLowerCase().trim();
            if (headerNameString.startsWith("x-amz-")) {
                String value = httpRequest.getHeader(headerName).trim();
                String[] parts = value.split("\n");
                value = "";
                for (String part : parts) {
                    part = part.trim();
                    value += part + " ";
                }
                value = value.trim();
                if (amzHeaders.containsKey(headerNameString)) {
                    String oldValue = (String) amzHeaders.remove(headerNameString);
                    oldValue += "," + value;
                    amzHeaders.put(headerNameString, oldValue);
                } else {
                    amzHeaders.put(headerNameString, value);
                }
            }
        }

        Iterator<String> iterator = amzHeaders.keySet().iterator();
        while (iterator.hasNext()) {
            String key = iterator.next();
            String value = (String) amzHeaders.get(key);
            result += key + ":" + value + "\n";
        }
        return result;
    }

    private void checkUploadPolicy(MappingHttpRequest httpRequest) throws AuthenticationException {
        Map<String, String> fields = new HashMap<String, String>();
        String policy = httpRequest.getAndRemoveHeader(WalrusProperties.Headers.S3UploadPolicy.toString());
        fields.put(WalrusProperties.FormField.policy.toString(), policy);
        String policySignature = httpRequest
                .getAndRemoveHeader(WalrusProperties.Headers.S3UploadPolicySignature.toString());
        if (policySignature == null)
            throw new AuthenticationException("Policy signature must be specified with policy.");
        String awsAccessKeyId = httpRequest.getAndRemoveHeader(SecurityParameter.AWSAccessKeyId.toString());
        if (awsAccessKeyId == null)
            throw new AuthenticationException("AWSAccessKeyID must be specified.");
        fields.put(WalrusProperties.FormField.signature.toString(), policySignature);
        fields.put(SecurityParameter.AWSAccessKeyId.toString(), awsAccessKeyId);
        String acl = httpRequest.getAndRemoveHeader(WalrusProperties.AMZ_ACL.toString());
        if (acl != null)
            fields.put(WalrusProperties.FormField.acl.toString(), acl);
        String operationPath = httpRequest.getServicePath().replaceAll(WalrusProperties.walrusServicePath, "");
        String[] target = WalrusUtil.getTarget(operationPath);
        if (target != null) {
            fields.put(WalrusProperties.FormField.bucket.toString(), target[0]);
            if (target.length > 1)
                fields.put(WalrusProperties.FormField.key.toString(), target[1]);
        }
        UploadPolicyChecker.checkPolicy(httpRequest, fields);

        String data = httpRequest.getAndRemoveHeader(WalrusProperties.FormField.FormUploadPolicyData.toString());
        String auth_part = httpRequest.getAndRemoveHeader(SecurityParameter.Authorization.toString());
        if (auth_part != null) {
            String sigString[] = getSigInfo(auth_part);
            if (sigString.length < 2) {
                throw new AuthenticationException("Invalid authentication header");
            }
            String accessKeyId = sigString[0];
            String signature = sigString[1];
            try {
                SecurityContext.getLoginContext(
                        new WalrusWrappedCredentials(httpRequest.getCorrelationId(), data, accessKeyId, signature))
                        .login();
            } catch (Exception ex) {
                LOG.error(ex);
                throw new AuthenticationException(ex);
            }
        } else {
            throw new AuthenticationException("User authentication failed. Invalid policy signature.");
        }

    }

    public void exceptionCaught(final ChannelHandlerContext ctx, final ExceptionEvent exceptionEvent)
            throws Exception {
        LOG.info("[exception " + exceptionEvent + "]");
        final HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.INTERNAL_SERVER_ERROR);
        DownstreamMessageEvent newEvent = new DownstreamMessageEvent(ctx.getChannel(),
                ctx.getChannel().getCloseFuture(), response, null);
        ctx.sendDownstream(newEvent);
        newEvent.getFuture().addListener(ChannelFutureListener.CLOSE);
    }

    @Override
    public void outgoingMessage(ChannelHandlerContext ctx, MessageEvent event) throws Exception {
    }
}