Make the JwtCookieLoginFilter an IdentityProvider instead of a filter.

Change-Id: I0107d66affe438739d5405bc33960a02e3bb9828
diff --git a/src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieLoginFilter.java b/src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieLoginFilter.java
index b39c90a..53903f7 100644
--- a/src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieLoginFilter.java
+++ b/src/main/java/eu/mulk/mulkcms2/authentication/JwtCookieLoginFilter.java
@@ -1,31 +1,30 @@
 package eu.mulk.mulkcms2.authentication;
 
-import static javax.ws.rs.Priorities.AUTHENTICATION;
-
+import io.quarkus.security.credential.Credential;
+import io.quarkus.security.identity.AuthenticationRequestContext;
+import io.quarkus.security.identity.IdentityProvider;
 import io.quarkus.security.identity.SecurityIdentity;
-import io.smallrye.jwt.auth.AbstractBearerTokenExtractor;
+import io.quarkus.security.identity.request.TokenAuthenticationRequest;
+import io.quarkus.smallrye.jwt.runtime.auth.JWTAuthMechanism;
 import io.smallrye.jwt.auth.principal.DefaultJWTCallerPrincipal;
-import io.smallrye.jwt.auth.principal.JWTAuthContextInfo;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
+import java.security.Permission;
 import java.security.Principal;
 import java.security.PublicKey;
-import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateException;
 import java.time.Duration;
+import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
 import javax.annotation.PostConstruct;
-import javax.annotation.Priority;
-import javax.inject.Inject;
-import javax.ws.rs.container.ContainerRequestContext;
-import javax.ws.rs.container.ContainerRequestFilter;
-import javax.ws.rs.core.SecurityContext;
-import javax.ws.rs.ext.Provider;
+import javax.enterprise.context.ApplicationScoped;
 import org.eclipse.microprofile.config.inject.ConfigProperty;
-import org.eclipse.microprofile.jwt.JsonWebToken;
 import org.jose4j.jwa.AlgorithmConstraints;
 import org.jose4j.jws.AlgorithmIdentifiers;
 import org.jose4j.jwt.MalformedClaimException;
@@ -41,11 +40,11 @@
  * way, there is no need to route the user through an OpenID Connect IdP on each request, for
  * example.
  *
+ * @see JWTAuthMechanism
  * @see JwtCookieSetterFilter
  */
-@Provider
-@Priority(AUTHENTICATION)
-public class JwtCookieLoginFilter implements ContainerRequestFilter {
+@ApplicationScoped
+public class JwtCookieLoginFilter implements IdentityProvider<TokenAuthenticationRequest> {
 
   @ConfigProperty(name = "mulkcms.jwt.signing-key")
   String signingKeyAlias;
@@ -62,18 +61,14 @@
   @ConfigProperty(name = "mulkcms.jwt.validity")
   Duration validity;
 
-  @Inject SecurityIdentity identity;
-
-  @Inject JWTAuthContextInfo authContextInfo;
-
   private static final Logger log = LoggerFactory.getLogger(JwtCookieLoginFilter.class);
 
   private PublicKey signingKey;
 
   @PostConstruct
   public void postCostruct()
-      throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException,
-          UnrecoverableKeyException {
+      throws IOException, KeyStoreException, CertificateException, NoSuchAlgorithmException {
+    log.info("Hello!");
     try (var is = new FileInputStream(signingKeyFile)) {
       var keystore = KeyStore.getInstance(KeyStore.getDefaultType());
       keystore.load(is, signingKeyPassphrase.toCharArray());
@@ -83,102 +78,107 @@
   }
 
   @Override
-  public void filter(ContainerRequestContext requestContext)
-      throws IOException {
+  public CompletionStage<SecurityIdentity> authenticate(
+      TokenAuthenticationRequest request, AuthenticationRequestContext context) {
 
-    try {
-      if (!identity.isAnonymous()) {
-        log.debug("Already authenticated, skipping JWT check.");
-        return;
-      }
+    log.info("Starting JWT verification.");
 
-      AbstractBearerTokenExtractor extractor =
-          new BearerTokenExtractor(requestContext, authContextInfo);
-      String bearerToken = extractor.getBearerToken();
+    return context.runBlocking(
+        () -> {
+          try {
+            log.info("JWT verification started.");
 
-      var jwtConsumer =
-          new JwtConsumerBuilder()
-              .setJwsAlgorithmConstraints(
-                  new AlgorithmConstraints(
-                      AlgorithmConstraints.ConstraintType.WHITELIST,
-                      AlgorithmIdentifiers.RSA_USING_SHA256,
-                      AlgorithmIdentifiers.RSA_USING_SHA384,
-                      AlgorithmIdentifiers.RSA_USING_SHA512,
-                      AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256,
-                      AlgorithmIdentifiers.ECDSA_USING_P384_CURVE_AND_SHA384,
-                      AlgorithmIdentifiers.ECDSA_USING_P521_CURVE_AND_SHA512))
-              .setVerificationKey(signingKey)
-              .setRequireExpirationTime()
-              .setAllowedClockSkewInSeconds(60)
-              .build();
+            /*
+            AbstractBearerTokenExtractor extractor =
+                new BearerTokenExtractor(requestContext, authContextInfo);
+            String bearerToken = extractor.getBearerToken();
+            */
 
-      var claims = jwtConsumer.process(bearerToken).getJwtClaims();
-      claims.getSubject();
+            // FIXME: But how does this know how the token is extracted?  What passes it here?
+            // Look up JWTAuthMechanism.
+            var bearerToken = request.getToken().getToken();
 
-      var jwtPrincipal = new DefaultJWTCallerPrincipal(claims);
-      log.debug("JWT verified: {}", jwtPrincipal);
+            var jwtConsumer =
+                new JwtConsumerBuilder()
+                    .setJwsAlgorithmConstraints(
+                        new AlgorithmConstraints(
+                            AlgorithmConstraints.ConstraintType.WHITELIST,
+                            AlgorithmIdentifiers.RSA_USING_SHA256,
+                            AlgorithmIdentifiers.RSA_USING_SHA384,
+                            AlgorithmIdentifiers.RSA_USING_SHA512,
+                            AlgorithmIdentifiers.ECDSA_USING_P256_CURVE_AND_SHA256,
+                            AlgorithmIdentifiers.ECDSA_USING_P384_CURVE_AND_SHA384,
+                            AlgorithmIdentifiers.ECDSA_USING_P521_CURVE_AND_SHA512))
+                    .setVerificationKey(signingKey)
+                    .setRequireExpirationTime()
+                    .setAllowedClockSkewInSeconds(60)
+                    .build();
 
-      var securityContext =
-          new JwtSecurityContext(requestContext.getSecurityContext(), jwtPrincipal);
-      requestContext.setSecurityContext(securityContext);
-    } catch (InvalidJwtException | MalformedClaimException e) {
-      log.debug("Invalid JWT", e);
-    }
+            var claims = jwtConsumer.process(bearerToken).getJwtClaims();
+            claims.getSubject();
+
+            var jwtPrincipal = new DefaultJWTCallerPrincipal(claims);
+            log.info("JWT verified: {}", jwtPrincipal);
+
+            return new CookieIdentity(jwtPrincipal);
+          } catch (InvalidJwtException | MalformedClaimException e) {
+            log.info("JWT verification failed", e);
+            return null;
+          }
+        });
   }
 
-  private static class BearerTokenExtractor extends AbstractBearerTokenExtractor {
+  @Override
+  public Class<TokenAuthenticationRequest> getRequestType() {
+    return TokenAuthenticationRequest.class;
+  }
 
-    private final ContainerRequestContext requestContext;
+  private static class CookieIdentity implements SecurityIdentity {
 
-    BearerTokenExtractor(
-        ContainerRequestContext requestContext, JWTAuthContextInfo authContextInfo) {
-      super(authContextInfo);
-      this.requestContext = requestContext;
+    private Principal jwtPrincipal;
+
+    private CookieIdentity(Principal jwtPrincipal) {
+      this.jwtPrincipal = jwtPrincipal;
     }
 
     @Override
-    protected String getHeaderValue(String headerName) {
-      return requestContext.getHeaderString(headerName);
+    public Principal getPrincipal() {
+      return jwtPrincipal;
     }
 
     @Override
-    protected String getCookieValue(String cookieName) {
-      var tokenCookie = requestContext.getCookies().get(cookieName);
+    public boolean isAnonymous() {
+      return false;
+    }
 
-      if (tokenCookie != null) {
-        return tokenCookie.getValue();
-      }
+    @Override
+    public Set<String> getRoles() {
+      return Set.of();
+    }
+
+    @Override
+    public <T extends Credential> T getCredential(Class<T> credentialType) {
       return null;
     }
-  }
 
-  private static class JwtSecurityContext implements SecurityContext {
-    private SecurityContext delegate;
-    private JsonWebToken principal;
-
-    JwtSecurityContext(SecurityContext delegate, JsonWebToken principal) {
-      this.delegate = delegate;
-      this.principal = principal;
+    @Override
+    public Set<Credential> getCredentials() {
+      return Set.of();
     }
 
     @Override
-    public Principal getUserPrincipal() {
-      return principal;
+    public <T> T getAttribute(String name) {
+      return null;
     }
 
     @Override
-    public boolean isUserInRole(String role) {
-      return principal.getGroups().contains(role);
+    public Map<String, Object> getAttributes() {
+      return Map.of();
     }
 
     @Override
-    public boolean isSecure() {
-      return delegate.isSecure();
-    }
-
-    @Override
-    public String getAuthenticationScheme() {
-      return delegate.getAuthenticationScheme();
+    public CompletionStage<Boolean> checkPermission(Permission permission) {
+      return CompletableFuture.completedFuture(false);
     }
   }
 }