diff --git a/core/src/main/java/org/keycloak/AbstractOAuthClient.java b/core/src/main/java/org/keycloak/AbstractOAuthClient.java index 9b15f7758a..aa65ad9dcc 100755 --- a/core/src/main/java/org/keycloak/AbstractOAuthClient.java +++ b/core/src/main/java/org/keycloak/AbstractOAuthClient.java @@ -17,6 +17,7 @@ public class AbstractOAuthClient { protected KeyStore truststore; protected String authUrl; protected String codeUrl; + protected String scope; protected String stateCookieName = OAUTH_TOKEN_REQUEST_STATE; protected String stateCookiePath; protected boolean isSecure; @@ -68,6 +69,14 @@ public class AbstractOAuthClient { this.codeUrl = codeUrl; } + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } + public String getStateCookieName() { return stateCookieName; } diff --git a/core/src/main/java/org/keycloak/adapters/ResourceMetadata.java b/core/src/main/java/org/keycloak/adapters/ResourceMetadata.java index dc79ff17c6..dd340b492c 100755 --- a/core/src/main/java/org/keycloak/adapters/ResourceMetadata.java +++ b/core/src/main/java/org/keycloak/adapters/ResourceMetadata.java @@ -14,6 +14,7 @@ public class ResourceMetadata { protected String clientKeyPassword; protected KeyStore truststore; protected PublicKey realmKey; + protected String scope; public String getResourceName() { return resourceName; @@ -78,4 +79,12 @@ public class ResourceMetadata { public void setRealmKey(PublicKey realmKey) { this.realmKey = realmKey; } + + public String getScope() { + return scope; + } + + public void setScope(String scope) { + this.scope = scope; + } } diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java index fb3a3194c5..46117dee09 100755 --- a/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java +++ b/core/src/main/java/org/keycloak/representations/adapters/config/AdapterConfig.java @@ -13,7 +13,7 @@ import org.codehaus.jackson.annotate.JsonPropertyOrder; "resource", "credentials", "use-resource-role-mappings", "enable-cors", "cors-max-age", "cors-allowed-methods", - "expose-token", "bearer-only", + "expose-token", "bearer-only", "scope", "connection-pool-size", "allow-any-hostname", "disable-trust-manager", "truststore", "truststore-password", "client-keystore", "client-keystore-password", "client-key-password" diff --git a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java index 498ec3837c..5ab9a7224c 100755 --- a/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java +++ b/core/src/main/java/org/keycloak/representations/adapters/config/BaseAdapterConfig.java @@ -2,6 +2,7 @@ package org.keycloak.representations.adapters.config; import org.codehaus.jackson.annotate.JsonProperty; import org.codehaus.jackson.annotate.JsonPropertyOrder; +import org.keycloak.representations.SkeletonKeyScope; import java.util.HashMap; import java.util.Map; @@ -16,7 +17,7 @@ import java.util.Map; "resource", "credentials", "use-resource-role-mappings", "enable-cors", "cors-max-age", "cors-allowed-methods", - "expose-token", "bearer-only"}) + "expose-token", "bearer-only", "scope"}) public class BaseAdapterConfig extends BaseRealmConfig { @JsonProperty("resource") protected String resource; @@ -36,6 +37,9 @@ public class BaseAdapterConfig extends BaseRealmConfig { protected boolean bearerOnly; @JsonProperty("credentials") protected Map credentials = new HashMap(); + @JsonProperty("scope") + protected SkeletonKeyScope scope; + public boolean isUseResourceRoleMappings() { return useResourceRoleMappings; @@ -108,4 +112,12 @@ public class BaseAdapterConfig extends BaseRealmConfig { public void setCredentials(Map credentials) { this.credentials = credentials; } + + public SkeletonKeyScope getScope() { + return scope; + } + + public void setScope(SkeletonKeyScope scope) { + this.scope = scope; + } } diff --git a/examples/as7-eap-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/CustomerService.java b/examples/as7-eap-demo/database-service/src/main/java/org/keycloak/example/oauth/CustomerService.java similarity index 92% rename from examples/as7-eap-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/CustomerService.java rename to examples/as7-eap-demo/database-service/src/main/java/org/keycloak/example/oauth/CustomerService.java index c6a0efc9db..a041476b44 100644 --- a/examples/as7-eap-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/CustomerService.java +++ b/examples/as7-eap-demo/database-service/src/main/java/org/keycloak/example/oauth/CustomerService.java @@ -1,4 +1,4 @@ -package org.jboss.resteasy.example.oauth; +package org.keycloak.example.oauth; import javax.ws.rs.GET; import javax.ws.rs.Path; diff --git a/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/DataApplication.java b/examples/as7-eap-demo/database-service/src/main/java/org/keycloak/example/oauth/DataApplication.java similarity index 85% rename from examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/DataApplication.java rename to examples/as7-eap-demo/database-service/src/main/java/org/keycloak/example/oauth/DataApplication.java index 673ad167e6..4a2ce3e565 100644 --- a/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/DataApplication.java +++ b/examples/as7-eap-demo/database-service/src/main/java/org/keycloak/example/oauth/DataApplication.java @@ -1,4 +1,4 @@ -package org.jboss.resteasy.example.oauth; +package org.keycloak.example.oauth; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; diff --git a/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/ProductService.java b/examples/as7-eap-demo/database-service/src/main/java/org/keycloak/example/oauth/ProductService.java similarity index 92% rename from examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/ProductService.java rename to examples/as7-eap-demo/database-service/src/main/java/org/keycloak/example/oauth/ProductService.java index 8515dfe934..c7f8f4d208 100644 --- a/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/ProductService.java +++ b/examples/as7-eap-demo/database-service/src/main/java/org/keycloak/example/oauth/ProductService.java @@ -1,4 +1,4 @@ -package org.jboss.resteasy.example.oauth; +package org.keycloak.example.oauth; import javax.ws.rs.GET; import javax.ws.rs.Path; diff --git a/examples/as7-eap-demo/pom.xml b/examples/as7-eap-demo/pom.xml index dcd6d576c0..fae3ad4253 100755 --- a/examples/as7-eap-demo/pom.xml +++ b/examples/as7-eap-demo/pom.xml @@ -38,5 +38,6 @@ product-app database-service third-party + third-party-cdi diff --git a/examples/as7-eap-demo/third-party-cdi/pom.xml b/examples/as7-eap-demo/third-party-cdi/pom.xml new file mode 100755 index 0000000000..8df4f5f427 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/pom.xml @@ -0,0 +1,74 @@ + + + + keycloak-parent + org.keycloak + 1.0-alpha-1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + org.keycloak.example.as7.demo + oauth-client-cdi-example + war + Simple OAuth Client Using CDI and JSF + + + + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.0_spec + 1.0.1.Final + provided + + + javax.enterprise + cdi-api + 1.0-SP4 + provided + + + org.jboss.spec.javax.faces + jboss-jsf-api_2.1_spec + 2.0.1.Final + provided + + + org.jboss.logging + jboss-logging + 3.1.2.GA + provided + + + org.keycloak + keycloak-servlet-oauth-client + ${project.version} + + + + + oauth-client-cdi + + + org.jboss.as.plugins + jboss-as-maven-plugin + 7.4.Final + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/AppContextListener.java b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/AppContextListener.java new file mode 100644 index 0000000000..f84019e530 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/AppContextListener.java @@ -0,0 +1,57 @@ +package org.keycloak.example.oauth; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import javax.inject.Inject; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +import org.jboss.logging.Logger; +import org.keycloak.servlet.ServletOAuthClient; +import org.keycloak.servlet.ServletOAuthClientConfigLoader; + +/** + * @author Marek Posolda + */ +@WebListener +public class AppContextListener implements ServletContextListener { + + private static final Logger logger = Logger.getLogger(AppContextListener.class); + + @Inject + private ServletOAuthClient oauthClient; + + @Override + public void contextInitialized(ServletContextEvent sce) { + ServletContext context = sce.getServletContext(); + + InputStream is = null; + String path = context.getInitParameter("keycloak.config.file"); + if (path == null) { + is = context.getResourceAsStream("/WEB-INF/keycloak.json"); + } else { + try { + is = new FileInputStream(path); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + + ServletOAuthClientConfigLoader loader = new ServletOAuthClientConfigLoader(is); + loader.initOAuthClientConfiguration(true); + loader.configureServletOAuthClient(oauthClient); + + oauthClient.start(); + logger.info("OAuth client configured and started"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + oauthClient.stop(); + logger.info("OAuth client stopped"); + } +} diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/CDIResourcesProducer.java b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/CDIResourcesProducer.java new file mode 100644 index 0000000000..fd5e45843e --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/CDIResourcesProducer.java @@ -0,0 +1,41 @@ +package org.keycloak.example.oauth; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Produces; +import javax.faces.context.FacesContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.keycloak.servlet.ServletOAuthClient; + +/** + * @author Marek Posolda + */ +public class CDIResourcesProducer { + + @Produces + @RequestScoped + public FacesContext produceFacesContext() { + return FacesContext.getCurrentInstance(); + } + + @Produces + @RequestScoped + @ServletRequestQualifier + public HttpServletRequest produceServletRequest() { + return (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest(); + } + + @Produces + @RequestScoped + public HttpServletResponse produceServletResponse() { + return (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse(); + } + + @Produces + @ApplicationScoped + public ServletOAuthClient produceOAuthClient() { + return new ServletOAuthClient(); + } +} diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java new file mode 100755 index 0000000000..17f7fd2832 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java @@ -0,0 +1,104 @@ +package org.keycloak.example.oauth; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.jboss.logging.Logger; +import org.keycloak.adapters.TokenGrantRequest; +import org.keycloak.servlet.ServletOAuthClient; +import org.keycloak.util.JsonSerialization; + +import javax.enterprise.context.ApplicationScoped; +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Bill Burke + * @author Marek Posolda + * @version $Revision: 1 $ + */ +@ApplicationScoped +@Named("databaseClient") +public class DatabaseClient { + + @Inject + @ServletRequestQualifier + private HttpServletRequest request; + + @Inject + private HttpServletResponse response; + + @Inject + private FacesContext facesContext; + + @Inject + private ServletOAuthClient oauthClient; + + @Inject + private UserData userData; + + private static final Logger logger = Logger.getLogger(DatabaseClient.class); + + public void retrieveAccessToken() { + try { + oauthClient.redirectRelative("client.jsf", request, response); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static class TypedList extends ArrayList {} + + public void sendCustomersRequest() { + List customers = sendRequestToDBApplication("http://localhost:8080/database/customers"); + userData.setCustomers(customers); + } + + public void sendProductsRequest() { + List products = sendRequestToDBApplication("http://localhost:8080/database/products"); + userData.setProducts(products); + } + + protected List sendRequestToDBApplication(String dbUri) { + HttpClient client = oauthClient.getClient(); + HttpGet get = new HttpGet(dbUri); + try { + + if (userData.isHasAccessToken()) { + get.addHeader("Authorization", "Bearer " + userData.getAccessToken()); + } + + HttpResponse response = client.execute(get); + switch (response.getStatusLine().getStatusCode()) { + case 200: HttpEntity entity = response.getEntity(); + InputStream is = entity.getContent(); + try { + return JsonSerialization.readValue(is, TypedList.class); + } finally { + is.close(); + } + case 401: facesContext.addMessage(null, new FacesMessage("Status: 401. Request not authenticated! You need to retrieve access token first.")); + break; + case 403: facesContext.addMessage(null, new FacesMessage("Status: 403. Access token has insufficient privileges")); + break; + default: facesContext.addMessage(null, new FacesMessage("Status: " + response.getStatusLine() + ". Not able to retrieve data. See log for details")); + logger.warn("Error occured. Status: " + response.getStatusLine()); + } + + return null; + } catch (IOException e) { + e.printStackTrace(); + facesContext.addMessage(null, new FacesMessage("Unknown error. See log for details")); + return null; + } + } +} diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/MessagesChecker.java b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/MessagesChecker.java new file mode 100644 index 0000000000..e59864f5bf --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/MessagesChecker.java @@ -0,0 +1,34 @@ +package org.keycloak.example.oauth; + +import javax.enterprise.context.RequestScoped; +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; + +/** + * This is needed because Faces context is not available in HTTP filters + * + * @author Marek Posolda + */ +@RequestScoped +@Named("messagesChecker") +public class MessagesChecker { + + @Inject + @ServletRequestQualifier + private HttpServletRequest request; + + @Inject + private FacesContext facesContext; + + public String getCheckMessage() { + String oauthError = (String)request.getAttribute(RefreshTokenFilter.OAUTH_ERROR_ATTR); + if (oauthError != null) { + facesContext.addMessage(null, new FacesMessage("OAuth error occured: " + oauthError)); + } + + return null; + } +} diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/RefreshTokenFilter.java b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/RefreshTokenFilter.java new file mode 100644 index 0000000000..7467959298 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/RefreshTokenFilter.java @@ -0,0 +1,62 @@ +package org.keycloak.example.oauth; + +import java.io.IOException; +import java.util.Map; + +import javax.inject.Inject; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.keycloak.adapters.TokenGrantRequest; +import org.keycloak.servlet.ServletOAuthClient; + +/** + * @author Marek Posolda + */ +@WebFilter(value = "/client.jsf") +public class RefreshTokenFilter implements Filter { + + public static final String OAUTH_ERROR_ATTR = "oauthErrorAttr"; + + @Inject + private ServletOAuthClient oauthClient; + + @Inject + private UserData userData; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)resp; + Map reqParams = request.getParameterMap(); + + if (reqParams.containsKey("code")) { + try { + String accessToken = oauthClient.getBearerToken(request); + userData.setAccessToken(accessToken); + } catch (TokenGrantRequest.HttpFailure e) { + throw new ServletException(e); + } + } else if (reqParams.containsKey("error")) { + String oauthError = reqParams.get("error")[0]; + request.setAttribute(OAUTH_ERROR_ATTR, oauthError); + } + + chain.doFilter(request, response); + } + + @Override + public void destroy() { + } +} diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/ServletRequestQualifier.java b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/ServletRequestQualifier.java new file mode 100644 index 0000000000..5f7edca722 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/ServletRequestQualifier.java @@ -0,0 +1,19 @@ +package org.keycloak.example.oauth; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +/** + * This is needed to have same code working in AS7 and Wildfly. In Wildfly is HttpServletRequest injected automatically, in AS7 it's not + * + * @author Marek Posolda + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) +public @interface ServletRequestQualifier { +} diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/UserData.java b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/UserData.java new file mode 100644 index 0000000000..6571b18f3a --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/UserData.java @@ -0,0 +1,63 @@ +package org.keycloak.example.oauth; + +import java.io.Serializable; +import java.util.List; + +import javax.enterprise.context.SessionScoped; +import javax.inject.Named; + +/** + * @author Marek Posolda + */ +@SessionScoped +@Named("userData") +public class UserData implements Serializable { + + private String accessToken; + private List products; + private List customers; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public boolean isHasAccessToken() { + return accessToken != null; + } + + public String getAccessTokenAvailabilityMessage() { + StringBuilder builder = new StringBuilder("Access token "); + if (!isHasAccessToken()) { + builder.append("not "); + } + return builder.append("available!").toString(); + } + + public List getProducts() { + return products; + } + + public void setProducts(List products) { + this.products = products; + } + + public boolean isHasProducts() { + return products != null; + } + + public List getCustomers() { + return customers; + } + + public void setCustomers(List customers) { + this.customers = customers; + } + + public boolean isHasCustomers() { + return customers != null; + } +} diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/beans.xml b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/beans.xml new file mode 100644 index 0000000000..c4feade019 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/beans.xml @@ -0,0 +1,23 @@ + + + + \ No newline at end of file diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/faces-config.xml b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/faces-config.xml new file mode 100644 index 0000000000..10787a09a9 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/faces-config.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/jboss-deployment-structure.xml new file mode 100755 index 0000000000..c54e4abec6 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/jboss-deployment-structure.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/keycloak.json b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/keycloak.json new file mode 100644 index 0000000000..7241bce584 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/keycloak.json @@ -0,0 +1,12 @@ +{ + "resource" : "third-party", + "auth-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/login", + "code-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes", + "ssl-not-required" : true, + "credentials" : { + "password" : "password" + }, + "scope": { + "realm": [ "user" ] + } +} \ No newline at end of file diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/web.xml b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/web.xml new file mode 100755 index 0000000000..93e1ef63c4 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,20 @@ + + + + oauth-client-cdi + + + + diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/webapp/client.xhtml b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/client.xhtml new file mode 100644 index 0000000000..7c4a8dd7c2 --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/client.xhtml @@ -0,0 +1,37 @@ + + + +

Third Party App That Pulls Data Using OAuth

+ + #{userData.accessTokenAvailabilityMessage} +
+ + + +
+ + +
+

Products data available

+ + #{product}
+
+
+ + +
+

Customers data available

+ + #{customer}
+
+
+ +
+ #{messagesChecker.checkMessage} + +
+ + diff --git a/examples/as7-eap-demo/third-party-cdi/src/main/webapp/index.html b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/index.html new file mode 100644 index 0000000000..7b641f77fd --- /dev/null +++ b/examples/as7-eap-demo/third-party-cdi/src/main/webapp/index.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/examples/as7-eap-demo/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java b/examples/as7-eap-demo/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java index 121af39b0a..7b8bec2a16 100755 --- a/examples/as7-eap-demo/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java +++ b/examples/as7-eap-demo/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java @@ -1,15 +1,19 @@ package org.keycloak.example.oauth; import org.keycloak.servlet.ServletOAuthClient; +import org.keycloak.servlet.ServletOAuthClientConfigLoader; +import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.security.KeyStore; /** - * Stupid init code to load up the truststore so we can make appropriate SSL connections + * Init code to load up the truststore so we can make appropriate SSL connections * You really should use a better way of initializing this stuff. * * @author Bill Burke @@ -32,36 +36,34 @@ public class Bootstrap implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { client = new ServletOAuthClient(); -/* - // hardcoded, WARNING, you should really have a better way of doing this - // configuration. Either use something like Spring or CDI, or even pull - // config vales from context-params - String truststorePath = "${jboss.server.config.dir}/client-truststore.ts"; - String truststorePassword = "password"; - truststorePath = EnvUtil.replace(truststorePath); - KeyStore truststore = null; - try - { - truststore = loadKeyStore(truststorePath, truststorePassword); - } - catch (Exception e) - { - throw new RuntimeException(e); - } - client.setTruststore(truststore); - */ - client.setClientId("third-party"); - client.setPassword("password"); - client.setAuthUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/login"); - client.setCodeUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes"); + ServletContext context = sce.getServletContext(); + + configureClient(context); + client.start(); - sce.getServletContext().setAttribute(ServletOAuthClient.class.getName(), client); - - + context.setAttribute(ServletOAuthClient.class.getName(), client); } @Override public void contextDestroyed(ServletContextEvent sce) { client.stop(); } + + private void configureClient(ServletContext context) { + InputStream is = null; + String path = context.getInitParameter("keycloak.config.file"); + if (path == null) { + is = context.getResourceAsStream("/WEB-INF/keycloak.json"); + } else { + try { + is = new FileInputStream(path); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + + ServletOAuthClientConfigLoader loader = new ServletOAuthClientConfigLoader(is); + loader.initOAuthClientConfiguration(true); + loader.configureServletOAuthClient(client); + } } diff --git a/examples/as7-eap-demo/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java b/examples/as7-eap-demo/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java index 2b4cbd5c72..a1983dc81e 100755 --- a/examples/as7-eap-demo/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java +++ b/examples/as7-eap-demo/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java @@ -21,11 +21,11 @@ import java.util.List; */ public class ProductDatabaseClient { public static void redirect(HttpServletRequest request, HttpServletResponse response) { - // This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute - // that is set in the Bootstrap context listenr in this project. + // The ServletOAuthClient is obtained by getting a context attribute + // that is set in the Bootstrap context listener in this project. // You really should come up with a better way to initialize // and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code - // and take a look how it works. + // and take a look how it works. You can also take a look at third-party-cdi example ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName()); try { oAuthClient.redirectRelative("pull_data.jsp", request, response); @@ -37,11 +37,11 @@ public class ProductDatabaseClient { static class TypedList extends ArrayList {} public static List getProducts(HttpServletRequest request) { - // This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute - // that is set in the Bootstrap context listenr in this project. + // The ServletOAuthClient is obtained by getting a context attribute + // that is set in the Bootstrap context listener in this project. // You really should come up with a better way to initialize // and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code - // and take a look how it works. + // and take a look how it works. You can also take a look at third-party-cdi example ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName()); String token = null; try { diff --git a/examples/as7-eap-demo/third-party/src/main/webapp/WEB-INF/keycloak.json b/examples/as7-eap-demo/third-party/src/main/webapp/WEB-INF/keycloak.json new file mode 100644 index 0000000000..7241bce584 --- /dev/null +++ b/examples/as7-eap-demo/third-party/src/main/webapp/WEB-INF/keycloak.json @@ -0,0 +1,12 @@ +{ + "resource" : "third-party", + "auth-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/login", + "code-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes", + "ssl-not-required" : true, + "credentials" : { + "password" : "password" + }, + "scope": { + "realm": [ "user" ] + } +} \ No newline at end of file diff --git a/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/CustomerService.java b/examples/wildfly-demo/database-service/src/main/java/org/keycloak/example/oauth/CustomerService.java similarity index 92% rename from examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/CustomerService.java rename to examples/wildfly-demo/database-service/src/main/java/org/keycloak/example/oauth/CustomerService.java index c6a0efc9db..a041476b44 100644 --- a/examples/wildfly-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/CustomerService.java +++ b/examples/wildfly-demo/database-service/src/main/java/org/keycloak/example/oauth/CustomerService.java @@ -1,4 +1,4 @@ -package org.jboss.resteasy.example.oauth; +package org.keycloak.example.oauth; import javax.ws.rs.GET; import javax.ws.rs.Path; diff --git a/examples/as7-eap-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/DataApplication.java b/examples/wildfly-demo/database-service/src/main/java/org/keycloak/example/oauth/DataApplication.java similarity index 85% rename from examples/as7-eap-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/DataApplication.java rename to examples/wildfly-demo/database-service/src/main/java/org/keycloak/example/oauth/DataApplication.java index 673ad167e6..4a2ce3e565 100644 --- a/examples/as7-eap-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/DataApplication.java +++ b/examples/wildfly-demo/database-service/src/main/java/org/keycloak/example/oauth/DataApplication.java @@ -1,4 +1,4 @@ -package org.jboss.resteasy.example.oauth; +package org.keycloak.example.oauth; import javax.ws.rs.ApplicationPath; import javax.ws.rs.core.Application; diff --git a/examples/as7-eap-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/ProductService.java b/examples/wildfly-demo/database-service/src/main/java/org/keycloak/example/oauth/ProductService.java similarity index 92% rename from examples/as7-eap-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/ProductService.java rename to examples/wildfly-demo/database-service/src/main/java/org/keycloak/example/oauth/ProductService.java index 8515dfe934..c7f8f4d208 100644 --- a/examples/as7-eap-demo/database-service/src/main/java/org/jboss/resteasy/example/oauth/ProductService.java +++ b/examples/wildfly-demo/database-service/src/main/java/org/keycloak/example/oauth/ProductService.java @@ -1,4 +1,4 @@ -package org.jboss.resteasy.example.oauth; +package org.keycloak.example.oauth; import javax.ws.rs.GET; import javax.ws.rs.Path; diff --git a/examples/wildfly-demo/pom.xml b/examples/wildfly-demo/pom.xml index 1d86e9e532..95444ebfaa 100755 --- a/examples/wildfly-demo/pom.xml +++ b/examples/wildfly-demo/pom.xml @@ -39,5 +39,6 @@ product-app database-service third-party + third-party-cdi diff --git a/examples/wildfly-demo/third-party-cdi/pom.xml b/examples/wildfly-demo/third-party-cdi/pom.xml new file mode 100755 index 0000000000..27784e333d --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/pom.xml @@ -0,0 +1,74 @@ + + + + keycloak-parent + org.keycloak + 1.0-alpha-1-SNAPSHOT + ../../../pom.xml + + 4.0.0 + org.keycloak.example.wildfly.demo + oauth-client-cdi-example + war + Simple OAuth Client Using CDI and JSF + + + + + org.jboss.spec.javax.servlet + jboss-servlet-api_3.0_spec + 1.0.1.Final + provided + + + javax.enterprise + cdi-api + 1.0-SP4 + provided + + + org.jboss.spec.javax.faces + jboss-jsf-api_2.1_spec + 2.0.1.Final + provided + + + org.jboss.logging + jboss-logging + 3.1.2.GA + provided + + + org.keycloak + keycloak-servlet-oauth-client + ${project.version} + + + + + oauth-client-cdi + + + org.jboss.as.plugins + jboss-as-maven-plugin + 7.4.Final + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.6 + 1.6 + + + + + diff --git a/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/AppContextListener.java b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/AppContextListener.java new file mode 100644 index 0000000000..f84019e530 --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/AppContextListener.java @@ -0,0 +1,57 @@ +package org.keycloak.example.oauth; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import javax.inject.Inject; +import javax.servlet.ServletContext; +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import javax.servlet.annotation.WebListener; + +import org.jboss.logging.Logger; +import org.keycloak.servlet.ServletOAuthClient; +import org.keycloak.servlet.ServletOAuthClientConfigLoader; + +/** + * @author Marek Posolda + */ +@WebListener +public class AppContextListener implements ServletContextListener { + + private static final Logger logger = Logger.getLogger(AppContextListener.class); + + @Inject + private ServletOAuthClient oauthClient; + + @Override + public void contextInitialized(ServletContextEvent sce) { + ServletContext context = sce.getServletContext(); + + InputStream is = null; + String path = context.getInitParameter("keycloak.config.file"); + if (path == null) { + is = context.getResourceAsStream("/WEB-INF/keycloak.json"); + } else { + try { + is = new FileInputStream(path); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + + ServletOAuthClientConfigLoader loader = new ServletOAuthClientConfigLoader(is); + loader.initOAuthClientConfiguration(true); + loader.configureServletOAuthClient(oauthClient); + + oauthClient.start(); + logger.info("OAuth client configured and started"); + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + oauthClient.stop(); + logger.info("OAuth client stopped"); + } +} diff --git a/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/CDIResourcesProducer.java b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/CDIResourcesProducer.java new file mode 100644 index 0000000000..fd5e45843e --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/CDIResourcesProducer.java @@ -0,0 +1,41 @@ +package org.keycloak.example.oauth; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.RequestScoped; +import javax.enterprise.inject.Produces; +import javax.faces.context.FacesContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.keycloak.servlet.ServletOAuthClient; + +/** + * @author Marek Posolda + */ +public class CDIResourcesProducer { + + @Produces + @RequestScoped + public FacesContext produceFacesContext() { + return FacesContext.getCurrentInstance(); + } + + @Produces + @RequestScoped + @ServletRequestQualifier + public HttpServletRequest produceServletRequest() { + return (HttpServletRequest)FacesContext.getCurrentInstance().getExternalContext().getRequest(); + } + + @Produces + @RequestScoped + public HttpServletResponse produceServletResponse() { + return (HttpServletResponse)FacesContext.getCurrentInstance().getExternalContext().getResponse(); + } + + @Produces + @ApplicationScoped + public ServletOAuthClient produceOAuthClient() { + return new ServletOAuthClient(); + } +} diff --git a/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java new file mode 100755 index 0000000000..17f7fd2832 --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/DatabaseClient.java @@ -0,0 +1,104 @@ +package org.keycloak.example.oauth; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.jboss.logging.Logger; +import org.keycloak.adapters.TokenGrantRequest; +import org.keycloak.servlet.ServletOAuthClient; +import org.keycloak.util.JsonSerialization; + +import javax.enterprise.context.ApplicationScoped; +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +/** + * @author Bill Burke + * @author Marek Posolda + * @version $Revision: 1 $ + */ +@ApplicationScoped +@Named("databaseClient") +public class DatabaseClient { + + @Inject + @ServletRequestQualifier + private HttpServletRequest request; + + @Inject + private HttpServletResponse response; + + @Inject + private FacesContext facesContext; + + @Inject + private ServletOAuthClient oauthClient; + + @Inject + private UserData userData; + + private static final Logger logger = Logger.getLogger(DatabaseClient.class); + + public void retrieveAccessToken() { + try { + oauthClient.redirectRelative("client.jsf", request, response); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + static class TypedList extends ArrayList {} + + public void sendCustomersRequest() { + List customers = sendRequestToDBApplication("http://localhost:8080/database/customers"); + userData.setCustomers(customers); + } + + public void sendProductsRequest() { + List products = sendRequestToDBApplication("http://localhost:8080/database/products"); + userData.setProducts(products); + } + + protected List sendRequestToDBApplication(String dbUri) { + HttpClient client = oauthClient.getClient(); + HttpGet get = new HttpGet(dbUri); + try { + + if (userData.isHasAccessToken()) { + get.addHeader("Authorization", "Bearer " + userData.getAccessToken()); + } + + HttpResponse response = client.execute(get); + switch (response.getStatusLine().getStatusCode()) { + case 200: HttpEntity entity = response.getEntity(); + InputStream is = entity.getContent(); + try { + return JsonSerialization.readValue(is, TypedList.class); + } finally { + is.close(); + } + case 401: facesContext.addMessage(null, new FacesMessage("Status: 401. Request not authenticated! You need to retrieve access token first.")); + break; + case 403: facesContext.addMessage(null, new FacesMessage("Status: 403. Access token has insufficient privileges")); + break; + default: facesContext.addMessage(null, new FacesMessage("Status: " + response.getStatusLine() + ". Not able to retrieve data. See log for details")); + logger.warn("Error occured. Status: " + response.getStatusLine()); + } + + return null; + } catch (IOException e) { + e.printStackTrace(); + facesContext.addMessage(null, new FacesMessage("Unknown error. See log for details")); + return null; + } + } +} diff --git a/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/MessagesChecker.java b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/MessagesChecker.java new file mode 100644 index 0000000000..e59864f5bf --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/MessagesChecker.java @@ -0,0 +1,34 @@ +package org.keycloak.example.oauth; + +import javax.enterprise.context.RequestScoped; +import javax.faces.application.FacesMessage; +import javax.faces.context.FacesContext; +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; + +/** + * This is needed because Faces context is not available in HTTP filters + * + * @author Marek Posolda + */ +@RequestScoped +@Named("messagesChecker") +public class MessagesChecker { + + @Inject + @ServletRequestQualifier + private HttpServletRequest request; + + @Inject + private FacesContext facesContext; + + public String getCheckMessage() { + String oauthError = (String)request.getAttribute(RefreshTokenFilter.OAUTH_ERROR_ATTR); + if (oauthError != null) { + facesContext.addMessage(null, new FacesMessage("OAuth error occured: " + oauthError)); + } + + return null; + } +} diff --git a/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/RefreshTokenFilter.java b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/RefreshTokenFilter.java new file mode 100644 index 0000000000..7467959298 --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/RefreshTokenFilter.java @@ -0,0 +1,62 @@ +package org.keycloak.example.oauth; + +import java.io.IOException; +import java.util.Map; + +import javax.inject.Inject; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.annotation.WebFilter; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.keycloak.adapters.TokenGrantRequest; +import org.keycloak.servlet.ServletOAuthClient; + +/** + * @author Marek Posolda + */ +@WebFilter(value = "/client.jsf") +public class RefreshTokenFilter implements Filter { + + public static final String OAUTH_ERROR_ATTR = "oauthErrorAttr"; + + @Inject + private ServletOAuthClient oauthClient; + + @Inject + private UserData userData; + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest)req; + HttpServletResponse response = (HttpServletResponse)resp; + Map reqParams = request.getParameterMap(); + + if (reqParams.containsKey("code")) { + try { + String accessToken = oauthClient.getBearerToken(request); + userData.setAccessToken(accessToken); + } catch (TokenGrantRequest.HttpFailure e) { + throw new ServletException(e); + } + } else if (reqParams.containsKey("error")) { + String oauthError = reqParams.get("error")[0]; + request.setAttribute(OAUTH_ERROR_ATTR, oauthError); + } + + chain.doFilter(request, response); + } + + @Override + public void destroy() { + } +} diff --git a/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/ServletRequestQualifier.java b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/ServletRequestQualifier.java new file mode 100644 index 0000000000..5f7edca722 --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/ServletRequestQualifier.java @@ -0,0 +1,19 @@ +package org.keycloak.example.oauth; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import javax.inject.Qualifier; + +/** + * This is needed to have same code working in AS7 and Wildfly. In Wildfly is HttpServletRequest injected automatically, in AS7 it's not + * + * @author Marek Posolda + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER }) +public @interface ServletRequestQualifier { +} diff --git a/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/UserData.java b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/UserData.java new file mode 100644 index 0000000000..6571b18f3a --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/java/org/keycloak/example/oauth/UserData.java @@ -0,0 +1,63 @@ +package org.keycloak.example.oauth; + +import java.io.Serializable; +import java.util.List; + +import javax.enterprise.context.SessionScoped; +import javax.inject.Named; + +/** + * @author Marek Posolda + */ +@SessionScoped +@Named("userData") +public class UserData implements Serializable { + + private String accessToken; + private List products; + private List customers; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public boolean isHasAccessToken() { + return accessToken != null; + } + + public String getAccessTokenAvailabilityMessage() { + StringBuilder builder = new StringBuilder("Access token "); + if (!isHasAccessToken()) { + builder.append("not "); + } + return builder.append("available!").toString(); + } + + public List getProducts() { + return products; + } + + public void setProducts(List products) { + this.products = products; + } + + public boolean isHasProducts() { + return products != null; + } + + public List getCustomers() { + return customers; + } + + public void setCustomers(List customers) { + this.customers = customers; + } + + public boolean isHasCustomers() { + return customers != null; + } +} diff --git a/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/beans.xml b/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/beans.xml new file mode 100644 index 0000000000..c4feade019 --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/beans.xml @@ -0,0 +1,23 @@ + + + + \ No newline at end of file diff --git a/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/faces-config.xml b/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/faces-config.xml new file mode 100644 index 0000000000..10787a09a9 --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/faces-config.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/jboss-deployment-structure.xml b/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/jboss-deployment-structure.xml new file mode 100755 index 0000000000..c54e4abec6 --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/jboss-deployment-structure.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/keycloak.json b/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/keycloak.json new file mode 100644 index 0000000000..7241bce584 --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/keycloak.json @@ -0,0 +1,12 @@ +{ + "resource" : "third-party", + "auth-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/login", + "code-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes", + "ssl-not-required" : true, + "credentials" : { + "password" : "password" + }, + "scope": { + "realm": [ "user" ] + } +} \ No newline at end of file diff --git a/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/web.xml b/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/web.xml new file mode 100755 index 0000000000..93e1ef63c4 --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,20 @@ + + + + oauth-client-cdi + + + + diff --git a/examples/wildfly-demo/third-party-cdi/src/main/webapp/client.xhtml b/examples/wildfly-demo/third-party-cdi/src/main/webapp/client.xhtml new file mode 100644 index 0000000000..7c4a8dd7c2 --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/webapp/client.xhtml @@ -0,0 +1,37 @@ + + + +

Third Party App That Pulls Data Using OAuth

+ + #{userData.accessTokenAvailabilityMessage} +
+ + + +
+ + +
+

Products data available

+ + #{product}
+
+
+ + +
+

Customers data available

+ + #{customer}
+
+
+ +
+ #{messagesChecker.checkMessage} + +
+ + diff --git a/examples/wildfly-demo/third-party-cdi/src/main/webapp/index.html b/examples/wildfly-demo/third-party-cdi/src/main/webapp/index.html new file mode 100644 index 0000000000..7b641f77fd --- /dev/null +++ b/examples/wildfly-demo/third-party-cdi/src/main/webapp/index.html @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/examples/wildfly-demo/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java b/examples/wildfly-demo/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java index 121af39b0a..7b8bec2a16 100755 --- a/examples/wildfly-demo/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java +++ b/examples/wildfly-demo/third-party/src/main/java/org/keycloak/example/oauth/Bootstrap.java @@ -1,15 +1,19 @@ package org.keycloak.example.oauth; import org.keycloak.servlet.ServletOAuthClient; +import org.keycloak.servlet.ServletOAuthClientConfigLoader; +import javax.servlet.ServletContext; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; import java.security.KeyStore; /** - * Stupid init code to load up the truststore so we can make appropriate SSL connections + * Init code to load up the truststore so we can make appropriate SSL connections * You really should use a better way of initializing this stuff. * * @author Bill Burke @@ -32,36 +36,34 @@ public class Bootstrap implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { client = new ServletOAuthClient(); -/* - // hardcoded, WARNING, you should really have a better way of doing this - // configuration. Either use something like Spring or CDI, or even pull - // config vales from context-params - String truststorePath = "${jboss.server.config.dir}/client-truststore.ts"; - String truststorePassword = "password"; - truststorePath = EnvUtil.replace(truststorePath); - KeyStore truststore = null; - try - { - truststore = loadKeyStore(truststorePath, truststorePassword); - } - catch (Exception e) - { - throw new RuntimeException(e); - } - client.setTruststore(truststore); - */ - client.setClientId("third-party"); - client.setPassword("password"); - client.setAuthUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/login"); - client.setCodeUrl("http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes"); + ServletContext context = sce.getServletContext(); + + configureClient(context); + client.start(); - sce.getServletContext().setAttribute(ServletOAuthClient.class.getName(), client); - - + context.setAttribute(ServletOAuthClient.class.getName(), client); } @Override public void contextDestroyed(ServletContextEvent sce) { client.stop(); } + + private void configureClient(ServletContext context) { + InputStream is = null; + String path = context.getInitParameter("keycloak.config.file"); + if (path == null) { + is = context.getResourceAsStream("/WEB-INF/keycloak.json"); + } else { + try { + is = new FileInputStream(path); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + } + + ServletOAuthClientConfigLoader loader = new ServletOAuthClientConfigLoader(is); + loader.initOAuthClientConfiguration(true); + loader.configureServletOAuthClient(client); + } } diff --git a/examples/wildfly-demo/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java b/examples/wildfly-demo/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java index 2b4cbd5c72..a1983dc81e 100755 --- a/examples/wildfly-demo/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java +++ b/examples/wildfly-demo/third-party/src/main/java/org/keycloak/example/oauth/ProductDatabaseClient.java @@ -21,11 +21,11 @@ import java.util.List; */ public class ProductDatabaseClient { public static void redirect(HttpServletRequest request, HttpServletResponse response) { - // This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute - // that is set in the Bootstrap context listenr in this project. + // The ServletOAuthClient is obtained by getting a context attribute + // that is set in the Bootstrap context listener in this project. // You really should come up with a better way to initialize // and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code - // and take a look how it works. + // and take a look how it works. You can also take a look at third-party-cdi example ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName()); try { oAuthClient.redirectRelative("pull_data.jsp", request, response); @@ -37,11 +37,11 @@ public class ProductDatabaseClient { static class TypedList extends ArrayList {} public static List getProducts(HttpServletRequest request) { - // This is really the worst code ever. The ServletOAuthClient is obtained by getting a context attribute - // that is set in the Bootstrap context listenr in this project. + // The ServletOAuthClient is obtained by getting a context attribute + // that is set in the Bootstrap context listener in this project. // You really should come up with a better way to initialize // and obtain the ServletOAuthClient. I actually suggest downloading the ServletOAuthClient code - // and take a look how it works. + // and take a look how it works. You can also take a look at third-party-cdi example ServletOAuthClient oAuthClient = (ServletOAuthClient) request.getServletContext().getAttribute(ServletOAuthClient.class.getName()); String token = null; try { diff --git a/examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/keycloak.json b/examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/keycloak.json new file mode 100644 index 0000000000..7241bce584 --- /dev/null +++ b/examples/wildfly-demo/third-party/src/main/webapp/WEB-INF/keycloak.json @@ -0,0 +1,12 @@ +{ + "resource" : "third-party", + "auth-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/login", + "code-url" : "http://localhost:8080/auth-server/rest/realms/demo/tokens/access/codes", + "ssl-not-required" : true, + "credentials" : { + "password" : "password" + }, + "scope": { + "realm": [ "user" ] + } +} \ No newline at end of file diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/AdapterConfigLoader.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/AdapterConfigLoader.java index 1b4245f3a8..8733ff9e38 100755 --- a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/AdapterConfigLoader.java +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/AdapterConfigLoader.java @@ -2,7 +2,10 @@ package org.keycloak.adapters.config; import org.codehaus.jackson.map.ObjectMapper; import org.codehaus.jackson.map.annotate.JsonSerialize; +import org.keycloak.representations.SkeletonKeyScope; +import org.keycloak.util.Base64Url; import org.keycloak.util.EnvUtil; +import org.keycloak.util.JsonSerialization; import org.keycloak.util.PemUtils; import org.keycloak.adapters.ResourceMetadata; import org.keycloak.representations.adapters.config.AdapterConfig; @@ -35,29 +38,8 @@ public class AdapterConfigLoader { } public void init() { - String truststorePath = adapterConfig.getTruststore(); - if (truststorePath != null) { - truststorePath = EnvUtil.replace(truststorePath); - String truststorePassword = adapterConfig.getTruststorePassword(); - truststorePath = null; - try { - this.truststore = loadKeyStore(truststorePath, truststorePassword); - } catch (Exception e) { - throw new RuntimeException("Failed to load truststore", e); - } - } - String clientKeystore = adapterConfig.getClientKeystore(); - String clientKeyPassword = null; - if (clientKeystore != null) { - clientKeystore = EnvUtil.replace(clientKeystore); - String clientKeystorePassword = adapterConfig.getClientKeystorePassword(); - clientCertKeystore = null; - try { - clientCertKeystore = loadKeyStore(clientKeystore, clientKeystorePassword); - } catch (Exception e) { - throw new RuntimeException("Failed to load keystore", e); - } - } + initTruststore(); + initClientKeystore(); String realm = adapterConfig.getRealm(); if (realm == null) throw new RuntimeException("Must set 'realm' in config"); @@ -81,10 +63,15 @@ public class AdapterConfigLoader { resourceMetadata.setResourceName(resource); resourceMetadata.setRealmKey(realmKey); resourceMetadata.setClientKeystore(clientCertKeystore); - clientKeyPassword = adapterConfig.getClientKeyPassword(); + String clientKeyPassword = adapterConfig.getClientKeyPassword(); resourceMetadata.setClientKeyPassword(clientKeyPassword); resourceMetadata.setTruststore(this.truststore); + if (adapterConfig.getScope() != null) { + String scope = encodeScope(adapterConfig.getScope()); + resourceMetadata.setScope(scope); + } + } public AdapterConfig getAdapterConfig() { @@ -113,4 +100,40 @@ public class AdapterConfigLoader { throw new RuntimeException(e); } } + + protected void initTruststore() { + String truststorePath = adapterConfig.getTruststore(); + if (truststorePath != null) { + truststorePath = EnvUtil.replace(truststorePath); + String truststorePassword = adapterConfig.getTruststorePassword(); + try { + this.truststore = loadKeyStore(truststorePath, truststorePassword); + } catch (Exception e) { + throw new RuntimeException("Failed to load truststore", e); + } + } + } + + protected void initClientKeystore() { + String clientKeystore = adapterConfig.getClientKeystore(); + if (clientKeystore != null) { + clientKeystore = EnvUtil.replace(clientKeystore); + String clientKeystorePassword = adapterConfig.getClientKeystorePassword(); + clientCertKeystore = null; + try { + clientCertKeystore = loadKeyStore(clientKeystore, clientKeystorePassword); + } catch (Exception e) { + throw new RuntimeException("Failed to load keystore", e); + } + } + } + + protected String encodeScope(SkeletonKeyScope scope) { + try { + byte[] scopeBytes = JsonSerialization.writeValueAsBytes(scope); + return Base64Url.encode(scopeBytes); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/integration/adapter-core/src/main/java/org/keycloak/adapters/config/OAuthClientConfigLoader.java b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/OAuthClientConfigLoader.java new file mode 100644 index 0000000000..98101b2e0d --- /dev/null +++ b/integration/adapter-core/src/main/java/org/keycloak/adapters/config/OAuthClientConfigLoader.java @@ -0,0 +1,38 @@ +package org.keycloak.adapters.config; + +import java.io.InputStream; + +import org.keycloak.AbstractOAuthClient; + +/** + * @author Marek Posolda + */ +public abstract class OAuthClientConfigLoader extends RealmConfigurationLoader { + + public OAuthClientConfigLoader() { + } + + public OAuthClientConfigLoader(InputStream is) { + super(is); + } + + /** + * For now, configure just things supported by AbstractOAuthClient + */ + public void initOAuthClientConfiguration() { + initTruststore(); + initClientKeystore(); + } + + public void configureOAuthClient(AbstractOAuthClient oauthClient) { + oauthClient.setClientId(adapterConfig.getResource()); + oauthClient.setPassword(adapterConfig.getCredentials().get("password")); + oauthClient.setAuthUrl(adapterConfig.getAuthUrl()); + oauthClient.setCodeUrl(adapterConfig.getCodeUrl()); + oauthClient.setTruststore(truststore); + if (adapterConfig.getScope() != null) { + String scope = encodeScope(adapterConfig.getScope()); + oauthClient.setScope(scope); + } + } +} diff --git a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java index 8d21b673fa..439447210a 100755 --- a/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java +++ b/integration/as7-eap6/adapter/src/main/java/org/keycloak/adapters/as7/ServletOAuthLogin.java @@ -138,12 +138,15 @@ public class ServletOAuthLogin { if (port != 443) secureUrl.port(port); url = secureUrl.build().toString(); } - return realmInfo.getAuthUrl().clone() + KeycloakUriBuilder uriBuilder = realmInfo.getAuthUrl().clone() .queryParam("client_id", realmInfo.getMetadata().getResourceName()) .queryParam("redirect_uri", url) .queryParam("state", state) - .queryParam("login", "true") - .build().toString(); + .queryParam("login", "true"); + if (realmInfo.getMetadata().getScope() != null) { + uriBuilder.queryParam("scope", realmInfo.getMetadata().getScope()); + } + return uriBuilder.build().toString(); } protected static final AtomicLong counter = new AtomicLong(); diff --git a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java index 8e0f640fd5..1858caac99 100755 --- a/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java +++ b/integration/jaxrs-oauth-client/src/main/java/org/keycloak/jaxrs/JaxrsOAuthClient.java @@ -18,6 +18,7 @@ import javax.ws.rs.core.Response; import javax.ws.rs.core.UriBuilder; import javax.ws.rs.core.UriInfo; import java.net.URI; +import java.net.URL; /** * Helper code to obtain oauth access tokens via browser redirects @@ -87,11 +88,15 @@ public class JaxrsOAuthClient extends AbstractOAuthClient { state += "#" + path; } - URI url = UriBuilder.fromUri(authUrl) + UriBuilder uriBuilder = UriBuilder.fromUri(authUrl) .queryParam("client_id", clientId) .queryParam("redirect_uri", redirectUri) - .queryParam("state", state) - .build(); + .queryParam("state", state); + if (scope != null) { + uriBuilder.queryParam("scope", scope); + } + URI url = uriBuilder.build(); + NewCookie cookie = new NewCookie(getStateCookieName(), state, getStateCookiePath(uriInfo), null, null, -1, isSecure, true); logger.debug("NewCookie: " + cookie.toString()); logger.debug("Oauth Redirect to: " + url); diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java index 8799bab92c..c8259c4b7a 100755 --- a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java +++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClient.java @@ -84,11 +84,15 @@ public class ServletOAuthClient extends AbstractOAuthClient { public void redirect(String redirectUri, HttpServletRequest request, HttpServletResponse response) throws IOException { String state = getStateCode(); - URI url = KeycloakUriBuilder.fromUri(authUrl) + KeycloakUriBuilder uriBuilder = KeycloakUriBuilder.fromUri(authUrl) .queryParam("client_id", clientId) .queryParam("redirect_uri", redirectUri) - .queryParam("state", state) - .build(); + .queryParam("state", state); + if (scope != null) { + uriBuilder.queryParam("scope", scope); + } + URI url = uriBuilder.build(); + String stateCookiePath = this.stateCookiePath; if (stateCookiePath == null) stateCookiePath = request.getContextPath(); if (stateCookiePath.equals("")) stateCookiePath = "/"; diff --git a/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientConfigLoader.java b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientConfigLoader.java new file mode 100644 index 0000000000..668a50c2b4 --- /dev/null +++ b/integration/servlet-oauth-client/src/main/java/org/keycloak/servlet/ServletOAuthClientConfigLoader.java @@ -0,0 +1,36 @@ +package org.keycloak.servlet; + +import java.io.InputStream; + +import org.keycloak.adapters.config.OAuthClientConfigLoader; + +/** + * @author Marek Posolda + */ +public class ServletOAuthClientConfigLoader extends OAuthClientConfigLoader { + + public ServletOAuthClientConfigLoader() { + } + + public ServletOAuthClientConfigLoader(InputStream is) { + super(is); + } + + /** + * For now, configure just things supported by ServletOAuthClient + * @param setupClient + */ + public void initOAuthClientConfiguration(boolean setupClient) { + initOAuthClientConfiguration(); + if (setupClient) { + initClient(); + } + } + + public void configureServletOAuthClient(ServletOAuthClient oauthClient) { + configureOAuthClient(oauthClient); + if (client != null) { + oauthClient.setClient(client); + } + } +}