diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi-private/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi-private/main/module.xml index 0f136fde63..be103dde61 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi-private/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-server-spi-private/main/module.xml @@ -36,5 +36,7 @@ + + diff --git a/server-spi-private/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java b/server-spi-private/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java index 024d78f049..005d64748c 100755 --- a/server-spi-private/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java +++ b/server-spi-private/src/main/java/org/keycloak/broker/provider/util/SimpleHttp.java @@ -17,16 +17,25 @@ package org.keycloak.broker.provider.util; -import org.apache.http.*; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.Header; +import org.apache.http.HeaderIterator; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.utils.URIBuilder; +import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicNameValuePair; import org.keycloak.connections.httpclient.HttpClientProvider; import org.keycloak.models.KeycloakSession; +import org.keycloak.util.JsonSerialization; import java.io.IOException; import java.io.InputStream; @@ -47,25 +56,36 @@ import java.util.zip.GZIPInputStream; */ public class SimpleHttp { - private KeycloakSession session; + private static final ObjectMapper mapper = new ObjectMapper(); + + private HttpClient client; private String url; private String method; private Map headers; private Map params; + private Object entity; - protected SimpleHttp(String url, String method, KeycloakSession session) { - this.session = session; + protected SimpleHttp(String url, String method, HttpClient client) { + this.client = client; this.url = url; this.method = method; } public static SimpleHttp doGet(String url, KeycloakSession session) { - return new SimpleHttp(url, "GET", session); + return doGet(url, session.getProvider(HttpClientProvider.class).getHttpClient()); + } + + public static SimpleHttp doGet(String url, HttpClient client) { + return new SimpleHttp(url, "GET", client); } public static SimpleHttp doPost(String url, KeycloakSession session) { - return new SimpleHttp(url, "POST", session); + return doPost(url, session.getProvider(HttpClientProvider.class).getHttpClient()); + } + + public static SimpleHttp doPost(String url, HttpClient client) { + return new SimpleHttp(url, "POST", client); } public SimpleHttp header(String name, String value) { @@ -76,6 +96,11 @@ public class SimpleHttp { return this; } + public SimpleHttp json(Object entity) { + this.entity = entity; + return this; + } + public SimpleHttp param(String name, String value) { if (params == null) { params = new HashMap(); @@ -84,43 +109,52 @@ public class SimpleHttp { return this; } - public String asString() throws IOException { - HttpClient httpClient = session.getProvider(HttpClientProvider.class).getHttpClient(); + public SimpleHttp auth(String token) { + header("Authorization", "Bearer " + token); + return this; + } - HttpResponse response = makeRequest(httpClient); - - InputStream is; - HttpEntity entity = response.getEntity(); - if (entity != null) { - is = entity.getContent(); - try { - HeaderIterator it = response.headerIterator(); - while (it.hasNext()) { - Header header = it.nextHeader(); - if (header.getName().equals("Content-Encoding") && header.getValue().equals("gzip")) { - is = new GZIPInputStream(is); - } - } - - return toString(is); - } finally { - if (is != null) { - is.close(); - } - } + public SimpleHttp acceptJson() { + if (headers == null || !headers.containsKey("Accept")) { + header("Accept", "application/json"); } - return null; + return this; + } + + public JsonNode asJson() throws IOException { + if (headers == null || !headers.containsKey("Accept")) { + header("Accept", "application/json"); + } + return mapper.readTree(asString()); + } + + public T asJson(Class type) throws IOException { + if (headers == null || !headers.containsKey("Accept")) { + header("Accept", "application/json"); + } + return JsonSerialization.readValue(asString(), type); + } + + public T asJson(TypeReference type) throws IOException { + if (headers == null || !headers.containsKey("Accept")) { + header("Accept", "application/json"); + } + return JsonSerialization.readValue(asString(), type); + } + + public String asString() throws IOException { + return asResponse().asString(); } public int asStatus() throws IOException { - HttpClient httpClient = session.getProvider(HttpClientProvider.class).getHttpClient(); - - HttpResponse response = makeRequest(httpClient); - - return response.getStatusLine().getStatusCode(); + return asResponse().getStatus(); } - private HttpResponse makeRequest(HttpClient httpClient) throws IOException { + public Response asResponse() throws IOException { + return makeRequest(); + } + + private Response makeRequest() throws IOException { boolean get = method.equals("GET"); boolean post = method.equals("POST"); @@ -130,7 +164,16 @@ public class SimpleHttp { } if (post) { - ((HttpPost) httpRequest).setEntity(getFormEntityFromParameter()); + if (params != null) { + ((HttpPost) httpRequest).setEntity(getFormEntityFromParameter()); + } else if (entity != null) { + if (headers == null || !headers.containsKey("Content-Type")) { + header("Content-Type", "application/json"); + } + ((HttpPost) httpRequest).setEntity(getJsonEntity()); + } else { + throw new IllegalStateException("No content set"); + } } if (headers != null) { @@ -139,7 +182,7 @@ public class SimpleHttp { } } - return httpClient.execute(httpRequest); + return new Response(client.execute(httpRequest)); } private URI appendParameterToUrl(String url) throws IOException { @@ -161,28 +204,93 @@ public class SimpleHttp { return uri; } + private StringEntity getJsonEntity() throws IOException { + return new StringEntity(JsonSerialization.writeValueAsString(entity)); + } + private UrlEncodedFormEntity getFormEntityFromParameter() throws IOException{ List urlParameters = new ArrayList<>(); if (params != null) { for (Map.Entry p : params.entrySet()) { - urlParameters.add(new BasicNameValuePair(p.getKey(), p.getValue())); + urlParameters. add(new BasicNameValuePair(p.getKey(), p.getValue())); } } return new UrlEncodedFormEntity(urlParameters); } - private String toString(InputStream is) throws IOException { - InputStreamReader reader = new InputStreamReader(is); + public static class Response { - StringWriter writer = new StringWriter(); + private HttpResponse response; + private int statusCode = -1; + private String responseString; - char[] buffer = new char[1024 * 4]; - for (int n = reader.read(buffer); n != -1; n = reader.read(buffer)) { - writer.write(buffer, 0, n); + public Response(HttpResponse response) { + this.response = response; } - return writer.toString(); + private void readResponse() throws IOException { + if (statusCode == -1) { + statusCode = response.getStatusLine().getStatusCode(); + + InputStream is; + HttpEntity entity = response.getEntity(); + if (entity != null) { + is = entity.getContent(); + try { + HeaderIterator it = response.headerIterator(); + while (it.hasNext()) { + Header header = it.nextHeader(); + if (header.getName().equals("Content-Encoding") && header.getValue().equals("gzip")) { + is = new GZIPInputStream(is); + } + } + + InputStreamReader reader = new InputStreamReader(is); + + StringWriter writer = new StringWriter(); + + char[] buffer = new char[1024 * 4]; + for (int n = reader.read(buffer); n != -1; n = reader.read(buffer)) { + writer.write(buffer, 0, n); + } + + responseString = writer.toString(); + } finally { + if (is != null) { + is.close(); + } + } + } + } + } + + public int getStatus() throws IOException { + readResponse(); + return response.getStatusLine().getStatusCode(); + } + + public JsonNode asJson() throws IOException { + return mapper.readTree(asString()); + } + + public T asJson(Class type) throws IOException { + return JsonSerialization.readValue(asString(), type); + } + + public T asJson(TypeReference type) throws IOException { + return JsonSerialization.readValue(asString(), type); + } + + public String asString() throws IOException { + readResponse(); + return responseString; + } + + public void close() throws IOException { + readResponse(); + } } + } diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java index 9daeb144fe..316d493749 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode; import org.jboss.logging.Logger; import org.keycloak.OAuth2Constants; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; -import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.provider.AuthenticationRequest; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; @@ -137,7 +136,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider=200 && status < 400; if (!success) { logger.warn("Failed backchannel broker logout to: " + url); @@ -368,9 +367,8 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProviderBill Burke - * @version $Revision: 1 $ - */ -public class JsonSimpleHttp extends SimpleHttp { - public JsonSimpleHttp(String url, String method, KeycloakSession session) { - super(url, method, session); - } - - public static JsonSimpleHttp doGet(String url, KeycloakSession session) { - return new JsonSimpleHttp(url, "GET", session); - } - - public static JsonSimpleHttp doPost(String url, KeycloakSession session) { - return new JsonSimpleHttp(url, "POST", session); - } - - private static ObjectMapper mapper = new ObjectMapper(); - - public static JsonNode asJson(SimpleHttp request) throws IOException { - return mapper.readTree(request.asString()); - } - -} diff --git a/services/src/main/java/org/keycloak/social/bitbucket/BitbucketIdentityProvider.java b/services/src/main/java/org/keycloak/social/bitbucket/BitbucketIdentityProvider.java index c7583b738e..bb7aa64102 100755 --- a/services/src/main/java/org/keycloak/social/bitbucket/BitbucketIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/bitbucket/BitbucketIdentityProvider.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.JsonNode; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; -import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; @@ -54,7 +53,7 @@ public class BitbucketIdentityProvider extends AbstractOAuth2IdentityProvider im @Override protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) { try { - JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(USER_URL, session).header("Authorization", "Bearer " + accessToken)); + JsonNode profile = SimpleHttp.doGet(USER_URL, session).header("Authorization", "Bearer " + accessToken).asJson(); String type = getJsonProperty(profile, "type"); if (type == null) { diff --git a/services/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java b/services/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java index bd6ca5497d..54be72c4ee 100755 --- a/services/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/facebook/FacebookIdentityProvider.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.JsonNode; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; -import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; @@ -47,7 +46,7 @@ public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider imp protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) { try { - JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL, session).header("Authorization", "Bearer " + accessToken)); + JsonNode profile = SimpleHttp.doGet(PROFILE_URL, session).header("Authorization", "Bearer " + accessToken).asJson(); String id = getJsonProperty(profile, "id"); diff --git a/services/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java b/services/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java index 71692937a0..4120e43292 100755 --- a/services/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.JsonNode; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; -import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; @@ -48,7 +47,7 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple @Override protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) { try { - JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL, session).header("Authorization", "Bearer " + accessToken)); + JsonNode profile = SimpleHttp.doGet(PROFILE_URL, session).header("Authorization", "Bearer " + accessToken).asJson(); BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id")); diff --git a/services/src/main/java/org/keycloak/social/gitlab/GitLabIdentityProvider.java b/services/src/main/java/org/keycloak/social/gitlab/GitLabIdentityProvider.java index a57704ff69..a35b4a3d42 100755 --- a/services/src/main/java/org/keycloak/social/gitlab/GitLabIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/gitlab/GitLabIdentityProvider.java @@ -18,14 +18,10 @@ package org.keycloak.social.gitlab; import com.fasterxml.jackson.databind.JsonNode; -import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; -import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.oidc.OIDCIdentityProvider; import org.keycloak.broker.oidc.OIDCIdentityProviderConfig; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; -import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.provider.BrokeredIdentityContext; -import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.social.SocialIdentityProvider; import org.keycloak.models.KeycloakSession; @@ -68,9 +64,8 @@ public class GitLabIdentityProvider extends OIDCIdentityProvider implements Soc if (getConfig().getDefaultScope().contains(API_SCOPE)) { String userInfoUrl = getUserInfoUrl(); if (userInfoUrl != null && !userInfoUrl.isEmpty() && (id == null || name == null || preferredUsername == null || email == null)) { - SimpleHttp request = JsonSimpleHttp.doGet(userInfoUrl, session) - .header("Authorization", "Bearer " + accessToken); - JsonNode userInfo = JsonSimpleHttp.asJson(request); + JsonNode userInfo = SimpleHttp.doGet(userInfoUrl, session) + .header("Authorization", "Bearer " + accessToken).asJson(); name = getJsonProperty(userInfo, "name"); preferredUsername = getJsonProperty(userInfo, "username"); diff --git a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java index d3befd704b..e25bcfa3bd 100755 --- a/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java @@ -21,7 +21,6 @@ import org.jboss.logging.Logger; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; -import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; @@ -57,7 +56,7 @@ public class LinkedInIdentityProvider extends AbstractOAuth2IdentityProvider imp protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) { log.debug("doGetFederatedIdentity()"); try { - JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(PROFILE_URL, session).header("Authorization", "Bearer " + accessToken)); + JsonNode profile = SimpleHttp.doGet(PROFILE_URL, session).header("Authorization", "Bearer " + accessToken).asJson(); BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id")); diff --git a/services/src/main/java/org/keycloak/social/microsoft/MicrosoftIdentityProvider.java b/services/src/main/java/org/keycloak/social/microsoft/MicrosoftIdentityProvider.java index 224733b2ed..17dde5edb8 100755 --- a/services/src/main/java/org/keycloak/social/microsoft/MicrosoftIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/microsoft/MicrosoftIdentityProvider.java @@ -22,13 +22,11 @@ import org.jboss.logging.Logger; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.OAuth2IdentityProviderConfig; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; -import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; import org.keycloak.broker.social.SocialIdentityProvider; -import com.fasterxml.jackson.databind.JsonNode; import org.keycloak.models.KeycloakSession; import java.net.URLEncoder; @@ -62,7 +60,7 @@ public class MicrosoftIdentityProvider extends AbstractOAuth2IdentityProvider im if (log.isDebugEnabled()) { log.debug("Microsoft Live user profile request to: " + URL); } - JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(URL, session)); + JsonNode profile = SimpleHttp.doGet(URL, session).asJson(); String id = getJsonProperty(profile, "id"); diff --git a/services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProvider.java b/services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProvider.java index bc83af133b..fafa42552b 100644 --- a/services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/openshift/OpenshiftV3IdentityProvider.java @@ -3,7 +3,6 @@ package org.keycloak.social.openshift; import com.fasterxml.jackson.databind.JsonNode; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; -import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; @@ -59,8 +58,9 @@ public class OpenshiftV3IdentityProvider extends AbstractOAuth2IdentityProvider< } private JsonNode fetchProfile(String accessToken) throws IOException { - return JsonSimpleHttp.asJson(SimpleHttp.doGet(getConfig().getUserInfoUrl(), this.session) - .header("Authorization", "Bearer " + accessToken)); + return SimpleHttp.doGet(getConfig().getUserInfoUrl(), this.session) + .header("Authorization", "Bearer " + accessToken) + .asJson(); } } diff --git a/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java b/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java index 43ccc6c603..9a0992a586 100755 --- a/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java +++ b/services/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java @@ -20,7 +20,6 @@ import com.fasterxml.jackson.databind.JsonNode; import org.jboss.logging.Logger; import org.keycloak.broker.oidc.AbstractOAuth2IdentityProvider; import org.keycloak.broker.oidc.mappers.AbstractJsonUserAttributeMapper; -import org.keycloak.broker.oidc.util.JsonSimpleHttp; import org.keycloak.broker.provider.BrokeredIdentityContext; import org.keycloak.broker.provider.IdentityBrokerException; import org.keycloak.broker.provider.util.SimpleHttp; @@ -63,7 +62,7 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide if (log.isDebugEnabled()) { log.debug("StackOverflow profile request to: " + URL); } - JsonNode profile = JsonSimpleHttp.asJson(SimpleHttp.doGet(URL, session)).get("items").get(0); + JsonNode profile = SimpleHttp.doGet(URL, session).asJson().get("items").get(0); BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id"));