keycloak-aplcache

KEYCLOAK-2777 - Added PathBasedKeycloakConfigResolver

4/19/2016 9:05:17 AM

Details

diff --git a/adapters/oidc/osgi-adapter/pom.xml b/adapters/oidc/osgi-adapter/pom.xml
index 2c8607e..6a24b29 100755
--- a/adapters/oidc/osgi-adapter/pom.xml
+++ b/adapters/oidc/osgi-adapter/pom.xml
@@ -48,6 +48,21 @@
 
     <dependencies>
         <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
             <groupId>org.jboss.logging</groupId>
             <artifactId>jboss-logging</artifactId>
             <scope>provided</scope>
diff --git a/adapters/oidc/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PathBasedKeycloakConfigResolver.java b/adapters/oidc/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PathBasedKeycloakConfigResolver.java
new file mode 100644
index 0000000..93d5cb6
--- /dev/null
+++ b/adapters/oidc/osgi-adapter/src/main/java/org/keycloak/adapters/osgi/PathBasedKeycloakConfigResolver.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.keycloak.adapters.osgi;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.keycloak.adapters.KeycloakConfigResolver;
+import org.keycloak.adapters.KeycloakDeployment;
+import org.keycloak.adapters.KeycloakDeploymentBuilder;
+import org.keycloak.adapters.OIDCHttpFacade;
+
+public class PathBasedKeycloakConfigResolver implements KeycloakConfigResolver {
+
+    private final Map<String, KeycloakDeployment> cache = new ConcurrentHashMap<String, KeycloakDeployment>();
+
+    @Override
+    public KeycloakDeployment resolve(OIDCHttpFacade.Request request) {
+        String path = request.getURI();
+        String[] urlTokens = path.split("/");
+        if (urlTokens.length <  4) {
+            throw new IllegalStateException("Not able to determine the web-context to load the correspondent keycloak.json file");
+        }
+
+        String webContext = urlTokens[3];
+
+        KeycloakDeployment deployment = cache.get(webContext);
+        if (null == deployment) {
+            // not found on the simple cache, try to load it from the file system
+            String keycloakConfig = (String) System.getProperties().get("keycloak.config");
+            if(keycloakConfig == null || "".equals(keycloakConfig.trim())){
+                String karafEtc = (String) System.getProperties().get("karaf.etc");
+                if(karafEtc == null || "".equals(karafEtc.trim())){
+                    throw new IllegalStateException("Neither \"keycloak.config\" nor \"karaf.etc\" java properties are set. Please set one of them.");
+                }
+                keycloakConfig = karafEtc;
+            }
+
+            String absolutePath = keycloakConfig + File.separator + webContext + "-keycloak.json";
+            InputStream is = null;
+            try {
+                is = new FileInputStream(absolutePath);
+            } catch (FileNotFoundException e){
+                throw new IllegalStateException("Not able to find the file " + absolutePath);
+            }
+            deployment = KeycloakDeploymentBuilder.build(is);
+            cache.put(webContext, deployment);
+        }
+
+        return deployment;
+    }
+
+}
diff --git a/examples/fuse/external-config/external-config-keycloak.json b/examples/fuse/external-config/external-config-keycloak.json
new file mode 100644
index 0000000..920e99a
--- /dev/null
+++ b/examples/fuse/external-config/external-config-keycloak.json
@@ -0,0 +1,10 @@
+{
+    "realm": "demo",
+    "resource": "external-config",
+    "realm-public-key": "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB",
+    "auth-server-url": "http://localhost:8080/auth",
+    "ssl-required" : "external",
+    "credentials": {
+        "secret": "password"
+    }
+}
diff --git a/examples/fuse/external-config/pom.xml b/examples/fuse/external-config/pom.xml
new file mode 100755
index 0000000..33ac617
--- /dev/null
+++ b/examples/fuse/external-config/pom.xml
@@ -0,0 +1,129 @@
+<!--
+  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+  ~ and other contributors as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <artifactId>keycloak-examples-parent</artifactId>
+        <groupId>org.keycloak</groupId>
+        <version>2.0.0.CR1-SNAPSHOT</version>
+    </parent>
+
+    <name>Keycloak Examples - External Config</name>
+    <artifactId>external-config</artifactId>
+    <packaging>war</packaging>
+    <groupId>org.keycloak.example.demo</groupId>
+    <description>
+        Keycloak External Config Example
+    </description>
+
+    <properties>
+        <keycloak.osgi.export>
+        </keycloak.osgi.export>
+        <keycloak.osgi.import>
+            org.apache.http.*;version=${apache.httpcomponents.version},
+            javax.servlet.*;version="[2.5,4)",
+            org.keycloak.adapters.jetty;version="${project.version}",
+            org.keycloak.adapters;version="${project.version}",
+            org.keycloak.constants;version="${project.version}",
+            org.keycloak.adapters.osgi;version="${project.version}",
+            org.keycloak.util;version="${project.version}",
+            org.keycloak.*;version="${project.version}",
+            *;resolution:=optional
+        </keycloak.osgi.import>
+        <keycloak.osgi.private>
+            org.keycloak.example.*
+        </keycloak.osgi.private>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.jboss.spec.javax.servlet</groupId>
+            <artifactId>jboss-servlet-api_3.0_spec</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Contains KeycloakDeployment and KeycloakConfigResolver -->
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-adapter-spi</artifactId>
+            <scope>provided</scope>
+        </dependency>
+
+        <!-- Contains KeycloakPrincipal -->
+        <dependency>
+            <groupId>org.keycloak</groupId>
+            <artifactId>keycloak-core</artifactId>
+            <scope>provided</scope>
+        </dependency>
+    </dependencies>
+    <build>
+        <finalName>external-config</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-war-plugin</artifactId>
+                <configuration>
+                    <archive>
+                        <manifestFile>${basedir}/target/classes/META-INF/MANIFEST.MF</manifestFile>
+                    </archive>
+                </configuration>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.felix</groupId>
+                <artifactId>maven-bundle-plugin</artifactId>
+                <extensions>true</extensions>
+                <executions>
+                    <execution>
+                        <id>bundle-manifest</id>
+                        <phase>process-classes</phase>
+                        <goals>
+                            <goal>manifest</goal>
+                        </goals>
+                    </execution>
+                </executions>
+                <configuration>
+                    <supportedProjectTypes>
+                        <supportedProjectType>war</supportedProjectType>
+                    </supportedProjectTypes>
+                    <instructions>
+                        <Webapp-Context>external-config</Webapp-Context>
+                        <Web-ContextPath>external-config</Web-ContextPath>
+                        <Embed-Directory>WEB-INF/lib</Embed-Directory>
+                        <Bundle-Name>${project.name}</Bundle-Name>
+                        <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+                        <Import-Package>${keycloak.osgi.import}</Import-Package>
+                        <Private-Package>${keycloak.osgi.private}</Private-Package>
+                        <Export-Package>${keycloak.osgi.export}</Export-Package>
+                    </instructions>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>
+
diff --git a/examples/fuse/external-config/README.md b/examples/fuse/external-config/README.md
new file mode 100644
index 0000000..91ad73a
--- /dev/null
+++ b/examples/fuse/external-config/README.md
@@ -0,0 +1,22 @@
+Keycloak Example - Externalized keycloak.json 
+=======================================
+
+The following example was tested on JBoss Fuse 6.3 and shows a way to package an OSGi compatible .war file that does not
+ include keycloak.json file in the .war archive but that automatically loads it based on a naming convention.
+ 
+To enable the functionality you need to add this section to your `web.xml`:
+
+```
+    <context-param>
+        <param-name>keycloak.config.resolver</param-name>
+        <param-value>org.keycloak.adapters.osgi.PathBasedKeycloakConfigResolver</param-value>
+    </context-param>
+```
+
+That component will use `keycloak.config` or `karaf.etc` java properties to look for a base folder to look for the configuration.
+ 
+Inside one of those folders it will look for a file called `<your_web_context>-keycloak.json`.
+
+For this example you need to copy the file `external-config-keycloak.json` to your JBoss Fuse `etc/` folder.
+
+Once you have done that, you can try to access the endpoint: http://localhost:8181/external-config/index.html
\ No newline at end of file
diff --git a/examples/fuse/external-config/src/main/java/org/keycloak/examples/ProtectedServlet.java b/examples/fuse/external-config/src/main/java/org/keycloak/examples/ProtectedServlet.java
new file mode 100755
index 0000000..ec30ccc
--- /dev/null
+++ b/examples/fuse/external-config/src/main/java/org/keycloak/examples/ProtectedServlet.java
@@ -0,0 +1,58 @@
+package org.keycloak.examples;/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import org.keycloak.KeycloakPrincipal;
+
+import javax.servlet.ServletException;
+import javax.servlet.annotation.WebServlet;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+
+@WebServlet(urlPatterns = "/servlet")
+public class ProtectedServlet extends HttpServlet {
+
+    @Override
+    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
+        String realm = req.getPathInfo().split("/")[1];
+        if (realm.contains("?")) {
+            realm = realm.split("\\?")[0];
+        }
+
+        if (req.getPathInfo().contains("logout")) {
+            req.logout();
+            resp.sendRedirect(req.getContextPath() + "/" + realm);
+            return;
+        }
+
+        KeycloakPrincipal principal = (KeycloakPrincipal) req.getUserPrincipal();
+
+        resp.setContentType("text/html");
+        PrintWriter writer = resp.getWriter();
+
+        writer.write("Realm: ");
+        writer.write(principal.getKeycloakSecurityContext().getRealm());
+
+        writer.write("<br/>User: ");
+        writer.write(principal.getKeycloakSecurityContext().getIdToken().getPreferredUsername());
+
+        writer.write(String.format("<br/><a href=\"/multitenant/%s/logout\">Logout</a>", realm));
+    }
+ }
diff --git a/examples/fuse/external-config/src/main/webapp/index.html b/examples/fuse/external-config/src/main/webapp/index.html
new file mode 100755
index 0000000..50ac1b6
--- /dev/null
+++ b/examples/fuse/external-config/src/main/webapp/index.html
@@ -0,0 +1,31 @@
+<!--
+  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+  ~ and other contributors as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+        "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+    <head>
+        <title>External Config Example Karaf/Fuse</title>
+    </head>
+    <body bgcolor="#E3F6CE">
+        <h1>External configuration worked.</h1>
+
+        <p><a href="http://localhost:8080/auth/realms/demo/protocol/openid-connect/logout">Log out</a></p>
+
+
+    </body>
+</html>
\ No newline at end of file
diff --git a/examples/fuse/external-config/src/main/webapp/WEB-INF/jetty-web.xml b/examples/fuse/external-config/src/main/webapp/WEB-INF/jetty-web.xml
new file mode 100644
index 0000000..6b42d3f
--- /dev/null
+++ b/examples/fuse/external-config/src/main/webapp/WEB-INF/jetty-web.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0"?>
+<!--
+  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+  ~ and other contributors as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure_9_0.dtd">
+<Configure class="org.eclipse.jetty.webapp.WebAppContext">
+    <Get name="securityHandler">
+        <Set name="authenticator">
+            <New class="org.keycloak.adapters.jetty.KeycloakJettyAuthenticator">
+            </New>
+        </Set>
+    </Get>
+</Configure>
\ No newline at end of file
diff --git a/examples/fuse/external-config/src/main/webapp/WEB-INF/web.xml b/examples/fuse/external-config/src/main/webapp/WEB-INF/web.xml
new file mode 100644
index 0000000..84aaa74
--- /dev/null
+++ b/examples/fuse/external-config/src/main/webapp/WEB-INF/web.xml
@@ -0,0 +1,55 @@
+<!--
+  ~ Copyright 2016 Red Hat, Inc. and/or its affiliates
+  ~ and other contributors as indicated by the @author tags.
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~ http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<web-app xmlns="http://java.sun.com/xml/ns/javaee"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+         version="3.0">
+    <module-name>External Config Example</module-name>
+
+    <context-param>
+        <param-name>keycloak.config.resolver</param-name>
+        <param-value>org.keycloak.adapters.osgi.PathBasedKeycloakConfigResolver</param-value>
+    </context-param>
+
+    <welcome-file-list>
+        <welcome-file>index.html</welcome-file>
+    </welcome-file-list>
+
+
+    <security-constraint>
+        <web-resource-collection>
+            <web-resource-name>REST endpoints</web-resource-name>
+            <url-pattern>/*</url-pattern>
+        </web-resource-collection>
+        <auth-constraint>
+            <role-name>user</role-name>
+        </auth-constraint>
+    </security-constraint>
+
+    <login-config>
+        <auth-method>BASIC</auth-method>
+        <realm-name>does-not-matter</realm-name>
+    </login-config>
+
+    <security-role>
+        <role-name>admin</role-name>
+    </security-role>
+    <security-role>
+        <role-name>user</role-name>
+    </security-role>
+</web-app>
\ No newline at end of file
diff --git a/examples/fuse/pom.xml b/examples/fuse/pom.xml
index 115effb..8920986 100755
--- a/examples/fuse/pom.xml
+++ b/examples/fuse/pom.xml
@@ -39,6 +39,7 @@
         <module>cxf-jaxws</module>
         <module>camel</module>
         <module>features</module>
+        <module>external-config</module>
     </modules>
 
 </project>
diff --git a/examples/fuse/testrealm.json b/examples/fuse/testrealm.json
index 88afec8..a27543a 100644
--- a/examples/fuse/testrealm.json
+++ b/examples/fuse/testrealm.json
@@ -186,7 +186,19 @@
             "standardFlowEnabled": false,
             "directAccessGrantsEnabled": true,
             "secret": "password"
+        },
+        {
+            "clientId": "external-config",
+            "enabled": true,
+            "adminUrl": "http://localhost:8181/external-config",
+            "baseUrl": "http://localhost:8181/external-config",
+            "redirectUris": [
+                "http://localhost:8181/external-config",
+                "http://localhost:8181/external-config/*"
+            ],
+            "secret": "password"
         }
+
     ],
     "scopeMappings": [
         {