package stroom.security.server;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.util.WebUtils;
import org.jose4j.jws.JsonWebSignature;
import org.jose4j.jwt.JwtClaims;
import org.jose4j.keys.HmacKey;
import org.jose4j.lang.JoseException;

import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;

import static org.jose4j.jws.AlgorithmIdentifiers.HMAC_SHA256;

public class JWTUtils {
    protected static final String AUTHORIZATION_HEADER = "Authorization";

    // TODO Move these to config
    public static final String ISSUER = "stroom";
    public static final String SECRET = "some-secret";

    public static Optional<String> getAuthHeader(ServletRequest request) {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        return(getAuthHeader(httpServletRequest));
    }

    public static Optional<String> getAuthHeader(HttpServletRequest httpServletRequest){
        String authHeader = httpServletRequest.getHeader(AUTHORIZATION_HEADER);
        return Strings.isNullOrEmpty(authHeader) ? Optional.empty() : Optional.of(authHeader);
    }

    public static Optional<AuthenticationToken> verifyToken(ServletRequest request){
        if (getAuthHeader(request).isPresent()) {
            String bearerString = getAuthHeader(request).get();
            // TODO This chops out 'Bearer'. We're dealing with unpredictable client data so we need a
            // way to do this that more robustly handles an error in the string.
            String jwtToken = bearerString.substring(7);
            return Optional.of(verifyToken(jwtToken));
        }
        else {
            return Optional.empty();
        }
    }

    public static String getTokenFor(String username) {
        return toToken(SECRET.getBytes(), getClaimsForUser(username));
    }

    private static JWTAuthenticationToken verifyToken(String token) {
        try {
            String subject = null;
            if (token != null) {
                JWTVerifier verifier = JWT
                        .require(Algorithm.HMAC256(SECRET))
                        .withIssuer(ISSUER)
                        .build();
                DecodedJWT jwt = verifier.verify(token);
                subject = jwt.getSubject();
            }

            return new JWTAuthenticationToken(subject, token);

        } catch (final Exception e) {
            throw new AuthenticationException(e);
        }
    }

    private static String toToken(byte[] key, JwtClaims claims) {
        final JsonWebSignature jws = new JsonWebSignature();
        jws.setPayload(claims.toJson());
        jws.setAlgorithmHeaderValue(HMAC_SHA256);
        jws.setKey(new HmacKey(key));
        jws.setDoKeyValidation(false);

        try {
            return jws.getCompactSerialization();
        }
        catch (JoseException e) { throw Throwables.propagate(e); }
    }

    private static JwtClaims getClaimsForUser(String user) {
        final JwtClaims claims = new JwtClaims();
        claims.setExpirationTimeMinutesInTheFuture(5);
        claims.setSubject(user);
        claims.setIssuer(ISSUER);
        return claims;
    }
}

@@ -545,16 +545,73 @@
 ception;
+%0Aimport org.slf4j.Logger;%0Aimport org.slf4j.LoggerFactory;
 %0A%0Aimport
@@ -803,16 +803,99 @@
 Utils %7B%0A
+    private static final Logger LOGGER = LoggerFactory.getLogger(JWTUtils.class);%0A%0A
     prot
@@ -1772,24 +1772,110 @@
 uest).get();
+%0A%0A            final String jwtToken;%0A%0A            if(bearerString.contains(%22Bearer%22))%7B
 %0A           
@@ -1871,24 +1871,28 @@
             
+    
 // TODO This
@@ -1982,16 +1982,20 @@
         
+    
 // way t
@@ -2067,22 +2067,19 @@
         
-String
+   
  jwtToke
@@ -2125,75 +2125,653 @@
     
-return Optional.of(verifyToken(jwtToken));%0A        %7D%0A        else %7B
+%7D%0A            else%7B%0A                jwtToken = bearerString;%0A            %7D%0A%0A            try %7B%0A                return Optional.of(verifyToken(jwtToken));%0A            %7D catch (Exception e)%7B%0A                LOGGER.error(%22Unable to verify token:%22, e.getMessage(), e);%0A                // If we get an exception verifying the token then we need to log the message%0A                // and continue as if the token wasn't provided.%0A                // TODO: decide if this should be handled by an exception and how%0A                return Optional.empty();%0A            %7D%0A        %7D%0A        else %7B%0A            // If there's no token then we've nothing to do.
 %0A   

Make auth header handling more defensive