package io.growingabit.jersey.filters;

import com.auth0.jwk.Jwk;
import com.auth0.jwk.JwkProvider;
import com.auth0.jwk.UrlJwkProvider;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.RSAKeyProvider;
import com.google.common.collect.ImmutableSet;
import io.growingabit.app.utils.ResourceFetcher;
import io.growingabit.app.utils.Settings;
import io.growingabit.app.utils.auth.Authorizer;
import io.growingabit.jersey.annotations.Secured;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Set;
import javax.annotation.Priority;
import javax.ws.rs.Priorities;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.Response;
import org.apache.commons.configuration2.Configuration;

@Secured
@Priority(Priorities.AUTHENTICATION)
public class SecurityFilter implements ContainerRequestFilter {

  private static final Configuration config = new Settings(new ResourceFetcher()).getConfig();
  private static final String OAUTH2_ISSUER = config.getString("oauth2.issuer");
  private static final String CUSTOM_CLAIMS_NAMESPACE = "https://growbit.io/";
  private static final String ROLES_CLAIM = CUSTOM_CLAIMS_NAMESPACE + "roles";
  private static final String JWK_URL = config.getString("jwk.url");

  @Override
  public void filter(final ContainerRequestContext requestContext) throws IOException {
    final String authorizationHeader = requestContext.getHeaderString(HttpHeaders.AUTHORIZATION);
    if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
      final String token = authorizationHeader.substring("Bearer".length()).trim();
      try {
        final DecodedJWT jwt = validateToken(token);

        final Set<String> roles = ImmutableSet.copyOf(jwt.getClaim(ROLES_CLAIM).asArray(String.class));

        // WTF??? that's is the user id...
        // I think .split("\\|")[1]; have killed some developer around the world... sorry
        final String userid = jwt.getSubject().split("\\|")[1];

        final String username = jwt.getClaim("nickname").asString();
        final boolean isSecure = requestContext.getSecurityContext().isSecure();

        final Authorizer authorizer = new Authorizer(userid, roles, username, isSecure);
        requestContext.setSecurityContext(authorizer);
      } catch (final Exception exception) {
        requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
      }
    } else {
      requestContext.abortWith(Response.status(Response.Status.UNAUTHORIZED).build());
    }
  }

  private DecodedJWT validateToken(final String token) throws Exception {
    try {
      final RSAKeyProvider keyProvider = new KeyProvider(new URL(JWK_URL));
      final Algorithm algorithm = Algorithm.RSA256(keyProvider);
      final JWTVerifier verifier = JWT.require(algorithm).withIssuer(OAUTH2_ISSUER).build();
      return verifier.verify(token);
    } catch (final MalformedURLException e) {
      return null;
    }
  }

  private class KeyProvider implements RSAKeyProvider {

    private final JwkProvider jwkProvider;

    // Here we can use some more intelligent JwkProvider, that could cache the key.
    // See https://github.com/auth0/jwks-rsa-java
    public KeyProvider(final URL url) {
      this.jwkProvider = new UrlJwkProvider(url);
    }

    @Override
    public RSAPublicKey getPublicKeyById(final String kid) {
      try {
        final Jwk jwk = this.jwkProvider.get(kid);
        final RSAPublicKey publicKey = (RSAPublicKey) jwk.getPublicKey();
        return (RSAPublicKey) publicKey;
      } catch (final Exception e) {
        e.printStackTrace();
        return null;
      }
    }

    @Override
    public RSAPrivateKey getPrivateKey() {
      // we can safely return null, beacuse we only validate tokens
      return null;
    }

    @Override
    public String getPrivateKeyId() {
      // we can safely return null, beacuse we only validate tokens
      return null;
    }
  }
}

@@ -33,16 +33,461 @@
 lters;%0A%0A
+import java.io.IOException;%0Aimport java.security.interfaces.RSAPrivateKey;%0Aimport java.security.interfaces.RSAPublicKey;%0Aimport java.util.Set;%0A%0Aimport javax.annotation.Priority;%0Aimport javax.ws.rs.Priorities;%0Aimport javax.ws.rs.container.ContainerRequestContext;%0Aimport javax.ws.rs.container.ContainerRequestFilter;%0Aimport javax.ws.rs.core.HttpHeaders;%0Aimport javax.ws.rs.core.Response;%0A%0Aimport org.apache.commons.configuration2.Configuration;%0A%0A
 import c
@@ -504,16 +504,16 @@
 wk.Jwk;%0A
-
 import c
@@ -813,24 +813,25 @@
 mutableSet;%0A
+%0A
 import io.gr
@@ -954,24 +954,24 @@
 Authorizer;%0A
+
 import io.gr
@@ -1011,510 +1011,8 @@
 red;
-%0Aimport java.io.IOException;%0Aimport java.net.MalformedURLException;%0Aimport java.net.URL;%0Aimport java.security.interfaces.RSAPrivateKey;%0Aimport java.security.interfaces.RSAPublicKey;%0Aimport java.util.Set;%0Aimport javax.annotation.Priority;%0Aimport javax.ws.rs.Priorities;%0Aimport javax.ws.rs.container.ContainerRequestContext;%0Aimport javax.ws.rs.container.ContainerRequestFilter;%0Aimport javax.ws.rs.core.HttpHeaders;%0Aimport javax.ws.rs.core.Response;%0Aimport org.apache.commons.configuration2.Configuration;
 %0A%0A@S
@@ -2880,28 +2880,16 @@
 ption %7B%0A
-    try %7B%0A  
     fina
@@ -2939,24 +2939,16 @@
 der(
-new URL(
 JWK_URL)
 );%0A 
@@ -2943,21 +2943,18 @@
 JWK_URL)
-)
 ;%0A
-  
     fina
@@ -3004,26 +3004,24 @@
 yProvider);%0A
-  
     final JW
@@ -3099,26 +3099,24 @@
 uild();%0A    
-  
 return verif
@@ -3138,79 +3138,8 @@
 n);%0A
-    %7D catch (final MalformedURLException e) %7B%0A      return null;%0A    %7D%0A
   %7D%0A
@@ -3403,19 +3403,22 @@
 r(final 
-URL
+String
  url) %7B%0A

Fix SecurityFilter