package de.otto.hmac.authentication;

import com.google.common.hash.HashCode;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteSource;
import org.apache.commons.codec.binary.Base64;
import org.joda.time.Instant;
import org.slf4j.Logger;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import static de.otto.hmac.HmacAttributes.X_HMAC_AUTH_DATE;
import static de.otto.hmac.HmacAttributes.X_HMAC_AUTH_SIGNATURE;
import static org.slf4j.LoggerFactory.getLogger;

public class RequestSigningUtil {

    private static final Logger LOG = getLogger(RequestSigningUtil.class);

    public static boolean checkRequest(final WrappedRequest request, final String secretKey) {

        if (!hasValidRequestTimeStamp(request)) {
            return false;
        }

        final String requestSignature = request.getHeader(X_HMAC_AUTH_SIGNATURE);

        final String[] split = requestSignature.split(":");
        final String sentSignature = split[1];

        final String generatedSignature = createRequestSignature(request, secretKey);

        return generatedSignature.equals(sentSignature);
    }

    public static boolean hasValidRequestTimeStamp(final WrappedRequest request) {
        final String requestTimeString = getDateFromHeader(request);
        if (requestTimeString == null || requestTimeString.isEmpty()) {
            LOG.error("Signierter Request enthält kein Datum.");
            return false;
        }

        final Instant serverTime = new Instant();
        final Instant requestTime = new Instant(requestTimeString);

        final long fiveMinutes = 60 * 5000L;

        final boolean inRange = requestTime.isAfter(serverTime.minus(fiveMinutes))
                && requestTime.isBefore(serverTime.plus(fiveMinutes));

        if (!inRange) {
            LOG.warn("Zeitstempel ausserhalb Serverzeit. Server: " + serverTime + ". Request: " + requestTimeString + ".");
        }

        return inRange;
    }

    public static String createSignatureBase(final WrappedRequest request) {
        return createSignatureBase(request.getMethod(), request.getHeader(X_HMAC_AUTH_DATE), request.getRequestURI(), request.getBody());
    }

    public static String createSignatureBase(final String method, final String dateHeader, final String requestUri, ByteSource body) {
        final StringBuilder builder = new StringBuilder();

        builder.append(method).append("\n");
        builder.append(dateHeader).append("\n");
        builder.append(requestUri).append("\n");
        builder.append(toMd5Hex(body));

        return builder.toString();
    }

    public static String createRequestSignature(final String method, final String dateHeader, final String requestUri, ByteSource body, final String secretKey) {
        final String signatureBase = createSignatureBase(method, dateHeader, requestUri, body);
        return createRequestSignature(signatureBase, secretKey);
    }

    public static String createRequestSignature(String signatureBase, String secretKey) {
        try {
            final SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
            final Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(keySpec);
            final byte[] result = mac.doFinal(signatureBase.getBytes());
            return encodeBase64WithoutLinefeed(result);
        }
        catch (final Exception e) {
            throw new RuntimeException("should never happen", e);
        }
    }

    public static String createRequestSignature(final WrappedRequest request, final String secretKey) {
        final String signatureBase = createSignatureBase(request);
        return createRequestSignature(signatureBase, secretKey);
    }

    protected static String encodeBase64WithoutLinefeed(byte[] result) {
        return Base64.encodeBase64String(result).trim();
    }

    public static MessageDigest getMD5Digest() {
        try {
            return MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("should never happen", e);
        }
    }

    public static boolean hasSignature(final HttpServletRequest request) {
        return request.getHeader(X_HMAC_AUTH_SIGNATURE) != null;
    }

    public static String getSignature(final HttpServletRequest request) {
        return request.getHeader(X_HMAC_AUTH_SIGNATURE);
    }

    public static String getDateFromHeader(final HttpServletRequest request) {
        final String header = request.getHeader(X_HMAC_AUTH_DATE);
        if (header == null) {
            return "";
        }
        return header;
    }

    public static String toMd5Hex(ByteSource byteSource) {
        try {
            HashCode md5 = byteSource.hash(Hashing.md5());
            return md5.toString();
        } catch (IOException e) {
            throw new RuntimeException("error evaluating md5 sum", e);
        }
    }
}

@@ -395,91 +395,8 @@
 ion;
-%0Aimport java.security.MessageDigest;%0Aimport java.security.NoSuchAlgorithmException;
 %0A%0Aim
@@ -906,47 +906,28 @@
 e = 
-request.getHeader(X_HMAC_AUTH_SIGNATURE
+getSignature(request
 );%0A%0A
@@ -2158,42 +2158,33 @@
 (), 
-request.getHeader(X_HMAC_AUTH_DATE
+getDateFromHeader(request
 ), r
@@ -3846,24 +3846,24 @@
 %5D result) %7B%0A
+
         retu
@@ -3918,254 +3918,8 @@
  %7D%0A%0A
-    public static MessageDigest getMD5Digest() %7B%0A        try %7B%0A            return MessageDigest.getInstance(%22MD5%22);%0A        %7D catch (NoSuchAlgorithmException e) %7B%0A            throw new RuntimeException(%22should never happen%22, e);%0A        %7D%0A    %7D%0A%0A
     

remove dead code