Package com.kolich.havalo.filters

Source Code of com.kolich.havalo.filters.HavaloAuthenticationFilter$HMACSHA256Signer

/**
* Copyright (c) 2013 Mark S. Kolich
* http://mark.koli.ch
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/

package com.kolich.havalo.filters;

import com.kolich.curacao.annotations.Injectable;
import com.kolich.curacao.handlers.requests.CuracaoRequestContext;
import com.kolich.curacao.handlers.requests.filters.CuracaoRequestFilter;
import com.kolich.havalo.components.RepositoryManagerComponent;
import com.kolich.havalo.entities.types.KeyPair;
import com.kolich.havalo.exceptions.authentication.AuthenticationException;
import com.kolich.havalo.exceptions.authentication.BadCredentialsException;
import org.slf4j.Logger;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.util.UUID;

import static com.google.common.net.HttpHeaders.*;
import static org.apache.commons.codec.binary.Base64.encodeBase64;
import static org.apache.commons.codec.binary.StringUtils.getBytesUtf8;
import static org.apache.commons.codec.binary.StringUtils.newStringUtf8;
import static org.apache.commons.io.IOUtils.LINE_SEPARATOR_UNIX;
import static org.slf4j.LoggerFactory.getLogger;

public final class HavaloAuthenticationFilter implements CuracaoRequestFilter {

  private static final Logger logger__ =
        getLogger(HavaloAuthenticationFilter.class);

  public static final String HAVALO_AUTHENTICATION_ATTRIBUTE =
        "havalo.authentication";

  private static final String HAVALO_AUTHORIZATION_PREFIX = "Havalo ";
  private static final String HAVALO_AUTHORIZATION_SEPARATOR = ":";

  private final HavaloUserService userService_;

    @Injectable
  public HavaloAuthenticationFilter(final RepositoryManagerComponent component) {
    userService_ = new HavaloUserService(component.getRepositoryManager());
  }

    @Override
    public final void filter(final CuracaoRequestContext context) throws Exception {
        try {
            final HttpServletRequest request = context.request_;
            // Extract the Authorization header from the incoming HTTP request.
            String header = request.getHeader(AUTHORIZATION);
            // If the header does not exist or does not start with the correct
            // token, give up immediately.
            if(header == null || !header.startsWith(HAVALO_AUTHORIZATION_PREFIX)) {
                throw new AuthenticationException("Request did not contain " +
                    "a valid '" + AUTHORIZATION + "' header.");
            }
            // Extract just the part of the Authorization header that follows
            // the Havalo authorization prefix.
            header = header.substring(HAVALO_AUTHORIZATION_PREFIX.length());
            final String[] tokens = header.split(HAVALO_AUTHORIZATION_SEPARATOR, 2);
            if(tokens == null || tokens.length != 2) {
                throw new AuthenticationException("Failed to extract correct " +
                    "number of tokens from '" + AUTHORIZATION + "' header.");
            }
            // If we get here, then we must have had some valid input
            // Authorization header with a real access key and signature.
            final String accessKey = tokens[0], signature = tokens[1];
            // request.getRequestURI();
            // Extract username from incoming signed request header.
            // Expected format is ...
            //    Authorization: Havalo AccessKey:Signature
            // ... where the AccessKey is the unique UUID used to identify the user.
            // And, the Signature is ...
            //    Base64( HMAC-SHA256( UTF-8-Encoding-Of( AccessSecret, StringToSign ) ) );
            // And, the StringToSign is ....
            //    HTTP-Verb (GET, PUT, POST, or DELETE) + "\n" +
            //    RFC822 Date (from 'Date' request header, must exist) + "\n" +
            //    Content-Type (from 'Content-Type' request header, optional) + "\n" +
            //    CanonicalizedResource (the part of this request's URL from
            //        the protocol name up to the query string in the first line
            //        of the HTTP request)
            // Call the user details service to load the user data for the UUID.
            final KeyPair userKp = userService_.loadKeyPairById(
                UUID.fromString(accessKey));
            if(userKp == null) {
                throw new AuthenticationException("User service returned " +
                    "null, which is an interface contract violation.");
            }
            // Get the string to sign -- will fail gracefully if the incoming
            // request does not have the proper headers attached to it.
            final String stringToSign = getStringToSign(request);
            // Compute the resulting signed signature.
            final String computed = HMACSHA256Signer.sign(userKp, stringToSign);
            // Does the signature match what was passed to us in the
            // Authorization request header?
            if(!computed.equals(signature)) {
                throw new BadCredentialsException("Signatures did not " +
                    "match (request=" + signature + ", computed=" + computed +
                    ")");
            }
            // Success!
            request.setAttribute(HAVALO_AUTHENTICATION_ATTRIBUTE, userKp);
        } catch (Exception e) {
            logger__.debug("Authentication failure; service failed " +
                "to authenticate request.", e);
            throw new AuthenticationException("Authentication " +
                "failed; either the provided signature did not match, " +
                "or you do not have permission to access the requested " +
                "resource.", e);
        }
    }
 
  private static final String getStringToSign(final HttpServletRequest request) {
    final StringBuilder sb = new StringBuilder();
    // HTTP-Verb (GET, PUT, POST, or DELETE) + "\n"
    sb.append(request.getMethod().toUpperCase()).append(LINE_SEPARATOR_UNIX);
    // RFC822 Date (from 'Date' request header, must exist) + "\n" +
    final String dateHeader;
    if((dateHeader = request.getHeader(DATE)) == null) {
      throw new BadCredentialsException("Incoming request missing " +
        "required '" + DATE + "' request header.");
    }
    sb.append(dateHeader).append(LINE_SEPARATOR_UNIX);
    // Content-Type (from 'Content-Type' request header, optional) + "\n" +
    final String contentType;
    if((contentType = request.getHeader(CONTENT_TYPE)) != null) {
      sb.append(contentType);
    }
    sb.append(LINE_SEPARATOR_UNIX);
    // CanonicalizedResource
    sb.append(request.getRequestURI());
    return sb.toString();
  }

    /**
   * Computes an HMAC-SHA256 signature.
   */
  private static final class HMACSHA256Signer {
   
    private static final String HMAC_SHA256_ALGORITHM_NAME = "HmacSHA256";
     
    /**
       * Returns a Base-64 encoded HMAC-SHA256 signature.
       */
    public static final String sign(final KeyPair kp,
                                        final String input) {
      try {
        // Get a new instance of the HMAC-SHA256 algorithm.
        final Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM_NAME);
        // Init it with our secret and the secret-key algorithm.
        mac.init(new SecretKeySpec(getBytesUtf8(kp.getSecret()),
          HMAC_SHA256_ALGORITHM_NAME));
        // Actually sign the input.
        return newStringUtf8(encodeBase64(mac.doFinal(
          getBytesUtf8(input))));
      } catch (Exception e) {
        throw new AuthenticationException("Failed to SHA-256 " +
          "sign input string: " + input, e);
      }
    }
   
  }

}
TOP

Related Classes of com.kolich.havalo.filters.HavaloAuthenticationFilter$HMACSHA256Signer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.