keycloak-aplcache

KEYCLOAK-1103 - First draft

5/27/2015 5:03:17 AM

Details

diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
index 58186dc..5108149 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintAuthorizationHandler.java
@@ -6,43 +6,58 @@ import io.undertow.util.HttpString;
 import org.keycloak.adapters.undertow.KeycloakUndertowAccount;
 import org.keycloak.representations.IDToken;
 
+import java.util.HashMap;
+import java.util.Map;
+
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
  * @version $Revision: 1 $
  */
 public class ConstraintAuthorizationHandler implements HttpHandler {
-    public static final HttpString KEYCLOAK_SUBJECT = new HttpString("KEYCLOAK-SUBJECT");
-    public static final HttpString KEYCLOAK_USERNAME = new HttpString("KEYCLOAK-USERNAME");
-    public static final HttpString KEYCLOAK_EMAIL = new HttpString("KEYCLOAK-EMAIL");
-    public static final HttpString KEYCLOAK_NAME = new HttpString("KEYCLOAK-NAME");
-    public static final HttpString KEYCLOAK_ACCESS_TOKEN = new HttpString("KEYCLOAK-ACCESS-TOKEN");
 
+    private final Map<String, HttpString> httpHeaderNames;
     protected HttpHandler next;
     protected String errorPage;
     protected boolean sendAccessToken;
 
-    public ConstraintAuthorizationHandler(HttpHandler next, String errorPage, boolean sendAccessToken) {
+    public ConstraintAuthorizationHandler(HttpHandler next, String errorPage, boolean sendAccessToken, Map<String, String> headerNames) {
         this.next = next;
         this.errorPage = errorPage;
         this.sendAccessToken = sendAccessToken;
+
+        this.httpHeaderNames = new HashMap<>();
+        this.httpHeaderNames.put("KEYCLOAK_SUBJECT", new HttpString(headerNames.getOrDefault("keycloak-subject", "KEYCLOAK_SUBJECT")));
+        this.httpHeaderNames.put("KEYCLOAK_USERNAME", new HttpString(headerNames.getOrDefault("keycloak-username", "KEYCLOAK_USERNAME")));
+        this.httpHeaderNames.put("KEYCLOAK_EMAIL", new HttpString(headerNames.getOrDefault("keycloak-email", "KEYCLOAK_EMAIL")));
+        this.httpHeaderNames.put("KEYCLOAK_NAME", new HttpString(headerNames.getOrDefault("keycloak-name", "KEYCLOAK_NAME")));
+        this.httpHeaderNames.put("KEYCLOAK_ACCESS_TOKEN", new HttpString(headerNames.getOrDefault("keycloak-access-token", "KEYCLOAK_ACCESS_TOKEN")));
     }
 
     @Override
     public void handleRequest(HttpServerExchange exchange) throws Exception {
+
         KeycloakUndertowAccount account = (KeycloakUndertowAccount)exchange.getSecurityContext().getAuthenticatedAccount();
+
         SingleConstraintMatch match = exchange.getAttachment(ConstraintMatcherHandler.CONSTRAINT_KEY);
         if (match == null || (match.getRequiredRoles().isEmpty() && match.getEmptyRoleSemantic() == SecurityInfo.EmptyRoleSemantic.AUTHENTICATE)) {
             authenticatedRequest(account, exchange);
             return;
         }
+
         if (match != null) {
-            for (String role : match.getRequiredRoles()) {
-                if (account.getRoles().contains(role)) {
-                    authenticatedRequest(account, exchange);
-                    return;
+            if(SecurityInfo.EmptyRoleSemantic.INJECT_IF_AUTHENTICATED.equals(match.getEmptyRoleSemantic())) {
+                authenticatedRequest(account, exchange);
+                return;
+            } else {
+                for (String role : match.getRequiredRoles()) {
+                    if (account.getRoles().contains(role)) {
+                        authenticatedRequest(account, exchange);
+                        return;
+                    }
                 }
             }
         }
+
         if (errorPage != null) {
             exchange.setRequestPath(errorPage);
             exchange.setRelativePath(errorPage);
@@ -61,20 +76,20 @@ public class ConstraintAuthorizationHandler implements HttpHandler {
             IDToken idToken = account.getKeycloakSecurityContext().getToken();
             if (idToken == null) return;
             if (idToken.getSubject() != null) {
-                exchange.getRequestHeaders().put(KEYCLOAK_SUBJECT, idToken.getSubject());
+                exchange.getRequestHeaders().put(httpHeaderNames.get("KEYCLOAK_SUBJECT"), idToken.getSubject());
             }
 
             if (idToken.getPreferredUsername() != null) {
-                exchange.getRequestHeaders().put(KEYCLOAK_USERNAME, idToken.getPreferredUsername());
+                exchange.getRequestHeaders().put(httpHeaderNames.get("KEYCLOAK_USERNAME"), idToken.getPreferredUsername());
             }
             if (idToken.getEmail() != null) {
-                exchange.getRequestHeaders().put(KEYCLOAK_EMAIL, idToken.getEmail());
+                exchange.getRequestHeaders().put(httpHeaderNames.get("KEYCLOAK_EMAIL"), idToken.getEmail());
             }
             if (idToken.getName() != null) {
-                exchange.getRequestHeaders().put(KEYCLOAK_NAME, idToken.getName());
+                exchange.getRequestHeaders().put(httpHeaderNames.get("KEYCLOAK_NAME"), idToken.getName());
             }
             if (sendAccessToken) {
-                exchange.getRequestHeaders().put(KEYCLOAK_ACCESS_TOKEN, account.getKeycloakSecurityContext().getTokenString());
+                exchange.getRequestHeaders().put(httpHeaderNames.get("KEYCLOAK_ACCESS_TOKEN"), account.getKeycloakSecurityContext().getTokenString());
             }
         }
         next.handleRequest(exchange);
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
index 998302d..f1347bb 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ConstraintMatcherHandler.java
@@ -1,11 +1,12 @@
 package org.keycloak.proxy;
 
-import io.undertow.security.handlers.AuthenticationConstraintHandler;
+import io.undertow.security.api.AuthenticationMechanism;
 import io.undertow.server.HttpHandler;
 import io.undertow.server.HttpServerExchange;
 import io.undertow.util.AttachmentKey;
 import org.jboss.logging.Logger;
-import org.keycloak.KeycloakSecurityContext;
+
+import java.util.List;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -47,10 +48,41 @@ public class ConstraintMatcherHandler implements HttpHandler {
             }
             return;
         }
+
+        if (match.getRequiredRoles().isEmpty() && match.getEmptyRoleSemantic() == SecurityInfo.EmptyRoleSemantic.INJECT_IF_AUTHENTICATED) {
+
+            boolean successfulAuthenticatedMethodFound = isSuccessfulAuthenticatedMethodFound(exchange);
+
+            if(successfulAuthenticatedMethodFound) {
+                //in case of authenticated we go for injecting headers
+                exchange.putAttachment(CONSTRAINT_KEY, match);
+                securedHandler.handleRequest(exchange);
+                return;
+            } else {
+                //in case of not authenticated we just show the resource
+                unsecuredHandler.handleRequest(exchange);
+                return;
+            }
+        }
+
         log.debug("found constraint");
         exchange.getSecurityContext().setAuthenticationRequired();
         exchange.putAttachment(CONSTRAINT_KEY, match);
         securedHandler.handleRequest(exchange);
 
     }
+
+    private boolean isSuccessfulAuthenticatedMethodFound(HttpServerExchange exchange) {
+        boolean successfulAuthenticatedMethodFound = false;
+        List<AuthenticationMechanism> authenticationMechanisms = exchange.getSecurityContext().getAuthenticationMechanisms();
+
+        for (AuthenticationMechanism authenticationMechanism : authenticationMechanisms) {
+            AuthenticationMechanism.AuthenticationMechanismOutcome authenticationMechanismOutcome =
+                    authenticationMechanism.authenticate(exchange, exchange.getSecurityContext());
+            if(authenticationMechanismOutcome.equals(AuthenticationMechanism.AuthenticationMechanismOutcome.AUTHENTICATED)) {
+                successfulAuthenticatedMethodFound = true;
+            }
+        }
+        return successfulAuthenticatedMethodFound;
+    }
 }
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java
index dd41a05..e0cdbab 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyConfig.java
@@ -3,10 +3,7 @@ package org.keycloak.proxy;
 import org.codehaus.jackson.annotate.JsonProperty;
 import org.keycloak.representations.adapters.config.AdapterConfig;
 
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -41,6 +38,8 @@ public class ProxyConfig {
     protected boolean sendAccessToken;
     @JsonProperty("applications")
     protected List<Application> applications = new LinkedList<Application>();
+    @JsonProperty("header-names")
+    private Map<String,String> headerNames = new HashMap<>();
 
     public String getBindAddress() {
         return bindAddress;
@@ -154,6 +153,14 @@ public class ProxyConfig {
         this.sendAccessToken = sendAccessToken;
     }
 
+    public void setHeaderNames(Map<String, String> headerNames) {
+        this.headerNames = headerNames;
+    }
+
+    public Map<String, String> getHeaderNames() {
+        return headerNames;
+    }
+
     public static class Application {
         @JsonProperty("base-path")
         protected String basePath;
@@ -212,6 +219,8 @@ public class ProxyConfig {
         protected boolean permit;
         @JsonProperty("authenticate")
         protected boolean authenticate;
+        @JsonProperty("inject-if-authenticated")
+        protected boolean injectIfAuthenticated;
 
         public String getPattern() {
             return pattern;
@@ -253,6 +262,14 @@ public class ProxyConfig {
             this.authenticate = authenticate;
         }
 
+        public boolean isInjectIfAuthenticated() {
+            return injectIfAuthenticated;
+        }
+
+        public void setInjectIfAuthenticated(boolean injectIfAuthenticated) {
+            this.injectIfAuthenticated = injectIfAuthenticated;
+        }
+
         public Set<String> getMethods() {
             return methods;
         }
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
index 1223faf..7e9eb2d 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/ProxyServerBuilder.java
@@ -51,10 +51,7 @@ import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.cert.Certificate;
 import java.security.cert.X509Certificate;
-import java.util.HashSet;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Set;
+import java.util.*;
 
 /**
  * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
@@ -76,6 +73,8 @@ public class ProxyServerBuilder {
     protected HttpHandler proxyHandler;
     protected boolean sendAccessToken;
 
+    protected Map<String, String> headerNameConfig;
+
     public ProxyServerBuilder target(String uri) {
         SimpleProxyClientProvider provider = null;
         try {
@@ -98,6 +97,12 @@ public class ProxyServerBuilder {
         this.sendAccessToken = flag;
         return this;
     }
+
+    public ProxyServerBuilder headerNameConfig(Map<String, String> headerNameConfig) {
+        this.headerNameConfig = headerNameConfig;
+        return this;
+    }
+
     public ApplicationBuilder application(AdapterConfig config) {
         return new ApplicationBuilder(config);
     }
@@ -169,6 +174,11 @@ public class ProxyServerBuilder {
                 return this;
             }
 
+            public ConstraintBuilder injectIfAuthenticated() {
+                semantic = SecurityInfo.EmptyRoleSemantic.INJECT_IF_AUTHENTICATED;
+                return this;
+            }
+
             public ConstraintBuilder excludedMethods(Set<String> excludedMethods) {
                 this.excludedMethods = excludedMethods;
                 return this;
@@ -222,7 +232,7 @@ public class ProxyServerBuilder {
                     errorPage = base + "/" + errorPage;
                 }
             }
-            handler = new ConstraintAuthorizationHandler(handler, errorPage, sendAccessToken);
+            handler = new ConstraintAuthorizationHandler(handler, errorPage, sendAccessToken, headerNameConfig);
             handler = new ProxyAuthenticationCallHandler(handler);
             handler = new ConstraintMatcherHandler(matches, handler, toWrap, errorPage);
             final List<AuthenticationMechanism> mechanisms = new LinkedList<AuthenticationMechanism>();
@@ -373,6 +383,7 @@ public class ProxyServerBuilder {
                     if (constraint.isDeny()) constraintBuilder.deny();
                     if (constraint.isPermit()) constraintBuilder.permit();
                     if (constraint.isAuthenticate()) constraintBuilder.authenticate();
+                    if (constraint.isInjectIfAuthenticated()) constraintBuilder.injectIfAuthenticated();
                     constraintBuilder.add();
                 }
             }
@@ -383,6 +394,7 @@ public class ProxyServerBuilder {
 
     public static void initOptions(ProxyConfig config, ProxyServerBuilder builder) {
         builder.sendAccessToken(config.isSendAccessToken());
+        builder.headerNameConfig(config.getHeaderNames());
         if (config.getBufferSize() != null) builder.setBufferSize(config.getBufferSize());
         if (config.getBuffersPerRegion() != null) builder.setBuffersPerRegion(config.getBuffersPerRegion());
         if (config.getIoThreads() != null) builder.setIoThreads(config.getIoThreads());
diff --git a/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityInfo.java b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityInfo.java
index 5d29137..a2eb420 100755
--- a/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityInfo.java
+++ b/proxy/proxy-server/src/main/java/org/keycloak/proxy/SecurityInfo.java
@@ -46,8 +46,12 @@ public class SecurityInfo<T extends SecurityInfo> implements Cloneable {
         /**
          * Mandate authentication but authorize access as no roles to check against.
          */
-        AUTHENTICATE;
+        AUTHENTICATE,
 
+        /**
+         * Permit access in any case, but provide authorization info only if authorized.
+         */
+        INJECT_IF_AUTHENTICATED;
     }
 
     private volatile EmptyRoleSemantic emptyRoleSemantic = EmptyRoleSemantic.DENY;