Java tutorial
/* * Copyright 2017 Danish Maritime Authority. * * 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 net.maritimecloud.identityregistry.utils; import lombok.extern.slf4j.Slf4j; import net.maritimecloud.identityregistry.exception.DuplicatedKeycloakEntry; import net.maritimecloud.identityregistry.model.database.IdentityProviderAttribute; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.keycloak.admin.client.Keycloak; import org.keycloak.admin.client.resource.IdentityProviderResource; import org.keycloak.admin.client.resource.RealmResource; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.CredentialRepresentation; import org.keycloak.representations.idm.IdentityProviderMapperRepresentation; import org.keycloak.representations.idm.IdentityProviderRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import javax.ws.rs.NotFoundException; import javax.ws.rs.core.Response; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @Component @Slf4j public class KeycloakAdminUtil { // Load the info needed to log into the Keycloak instance that is used as ID Broker (hosts ID Providers) @Value("${net.maritimecloud.idreg.keycloak-broker-admin-user}") private String keycloakBrokerAdminUser; @Value("${net.maritimecloud.idreg.keycloak-broker-admin-password}") private String keycloakBrokerAdminPassword; @Value("${net.maritimecloud.idreg.keycloak-broker-admin-client}") private String keycloakBrokerAdminClient; @Value("${net.maritimecloud.idreg.keycloak-broker-realm}") private String keycloakBrokerRealm; @Value("${net.maritimecloud.idreg.keycloak-broker-base-url}") private String keycloakBrokerBaseUrl; // Load the info needed to log into the Keycloak instance that is used as to host project users @Value("${net.maritimecloud.idreg.keycloak-project-users-admin-user}") private String keycloakProjectUsersAdminUser; @Value("${net.maritimecloud.idreg.keycloak-project-users-admin-password}") private String keycloakProjectUsersAdminPassword; @Value("${net.maritimecloud.idreg.keycloak-project-users-admin-client}") private String keycloakProjectUsersAdminClient; @Value("${net.maritimecloud.idreg.keycloak-project-users-realm}") private String keycloakProjectUsersRealm; @Value("${net.maritimecloud.idreg.keycloak-project-users-base-url}") private String keycloakProjectUsersBaseUrl; // Load client template name used when creating clients in keycloak @Value("${net.maritimecloud.idreg.keycloak-client-template}") private String keycloakClientTemplate; // Type of user public static final int NORMAL_USER = 0; public static final int ADMIN_USER = 1; // Type of instance public static final int BROKER_INSTANCE = 0; public static final int USER_INSTANCE = 1; private Keycloak keycloakBrokerInstance = null; private Keycloak keycloakUserInstance = null; // Used in createIdpMapper private static final Map<String, String> oidcDefaultMappers = new HashMap<>(); static { oidcDefaultMappers.put("firstNameAttr", null); oidcDefaultMappers.put("lastNameAttr", null); oidcDefaultMappers.put("emailAttr", null); oidcDefaultMappers.put("permissionsAttr", "permissions"); } private static final Map<String, String> samlDefaultMappers = new HashMap<>(); static { samlDefaultMappers.put("firstNameAttr", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"); samlDefaultMappers.put("lastNameAttr", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"); samlDefaultMappers.put("emailAttr", "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"); samlDefaultMappers.put("permissionsAttr", "http://schemas.microsoft.com/ws/2008/06/identity/claims/role"); } private static final Map<String, String> attrNames2Keycloak = new HashMap<>(); static { attrNames2Keycloak.put("firstNameAttr", "firstName"); attrNames2Keycloak.put("lastNameAttr", "lastName"); attrNames2Keycloak.put("emailAttr", "email"); attrNames2Keycloak.put("permissionsAttr", "permissions"); } /** * Constructor. */ public KeycloakAdminUtil() { } /** * Init the keycloak instance. Will only initialize the instance defined by the type * * @param type The type of instance to initialize. */ public void init(int type) { //keycloakInstance = Keycloak.getInstance(deployment.getAuthServerBaseUrl(), deployment.getRealm(), "idreg-admin", "idreg-admin", "mcidreg", "1b1f1686-1391-4b25-b770-906a2ffc7db9"); //keycloakInstance = Keycloak.getInstance(keycloakBaseUrl, keycloakRealm, "idreg-admin", "idreg-admin", "security-admin-console"); if (type == BROKER_INSTANCE) { keycloakBrokerInstance = Keycloak.getInstance(keycloakBrokerBaseUrl, keycloakBrokerRealm, keycloakBrokerAdminUser, keycloakBrokerAdminPassword, keycloakBrokerAdminClient); } else if (type == USER_INSTANCE) { keycloakUserInstance = Keycloak.getInstance(keycloakProjectUsersBaseUrl, keycloakProjectUsersRealm, keycloakProjectUsersAdminUser, keycloakProjectUsersAdminPassword, keycloakProjectUsersAdminClient); } } private RealmResource getBrokerRealm() { return keycloakBrokerInstance.realm(keycloakBrokerRealm); } private RealmResource getProjectUserRealm() { return keycloakUserInstance.realm(keycloakProjectUsersRealm); } /** * Get IDP info by parsing info from wellKnownUrl json * * @param infoUrl The url to parse * @param providerId The provider type, can be "keycloak-oidc","oidc" or "saml" * @return The IDP * @throws IOException */ private Map<String, String> getIdpSetupUrl(String infoUrl, String providerId) throws IOException { // Get IDP info by using keycloaks builtin parser Map<String, Object> importFromUrl = new HashMap<>(); importFromUrl.put("fromUrl", infoUrl); importFromUrl.put("providerId", providerId); // providerId can be either "keycloak-oidc", "oidc" or "saml" Map<String, String> importConf = getBrokerRealm().identityProviders().importFrom(importFromUrl); // Do some checks to validate the returned if (importConf == null || importConf.isEmpty()) { throw new IOException("Could not find needed information using the provided URL!"); } return importConf; } private Map<String, String> idpAttributes2Map(List<IdentityProviderAttribute> input) { log.debug("In idpAttributes2Map, number of attrs: " + input.size()); Map<String, String> ret = new HashMap<>(); for (IdentityProviderAttribute atr : input) { ret.put(atr.getAttributeName(), atr.getAttributeValue()); log.debug("idp attr name: " + atr.getAttributeName() + ", value: " + atr.getAttributeValue()); } return ret; } /** * Creates or updates an IDP. * * @param orgMrn mrn of the IDP * @param input map containing data about the IDP * @throws IOException */ public void createIdentityProvider(String orgMrn, List<IdentityProviderAttribute> input) throws IOException { String name = MrnUtil.getOrgShortNameFromOrgMrn(orgMrn); Map<String, String> idpAtrMap = idpAttributes2Map(input); // Check for valid input String providerType = idpAtrMap.get("providerType"); if (providerType == null || providerType.isEmpty()) { throw new IllegalArgumentException("Missing providerType"); } if (!"oidc".equals(providerType) && !"saml".equals(providerType)) { throw new IllegalArgumentException("Illegal providerType, must be \"oidc\" or \"saml\""); } // Get data from URL if supplied Map<String, String> importConf; if (idpAtrMap.containsKey("importUrl")) { importConf = getIdpSetupUrl(idpAtrMap.get("importUrl"), idpAtrMap.get("providerType")); } else { importConf = new HashMap<>(idpAtrMap); importConf.remove("providerType"); } if ("oidc".equals(providerType)) { // Check for valid input String clientId = idpAtrMap.get("clientId"); String clientSecret = idpAtrMap.get("clientSecret"); if (clientId == null || clientId.isEmpty()) { throw new IllegalArgumentException("Missing clientId"); } if (clientSecret == null || clientSecret.isEmpty()) { throw new IllegalArgumentException("Missing clientSecret"); } importConf.put("clientId", clientId); importConf.put("clientSecret", clientSecret); } // Insert data into IDP data structure IdentityProviderRepresentation idp = new IdentityProviderRepresentation(); idp.setAlias(name); idp.setEnabled(true); idp.setProviderId(providerType); // can be "keycloak-oidc","oidc" or "saml" idp.setTrustEmail(true); idp.setStoreToken(false); idp.setAddReadTokenRoleOnCreate(false); idp.setFirstBrokerLoginFlowAlias("Auto first broker login"); idp.setConfig(importConf); // Check if the IDP already exists IdentityProviderResource oldIdpRes = getBrokerRealm().identityProviders().get(name); IdentityProviderRepresentation oldIdp = null; try { oldIdp = oldIdpRes.toRepresentation(); } catch (NotFoundException nfe) { log.warn("Unable to convert old IDP"); } // todo the code below should be moved inside the try and catch block, there is no reason for the oldIdp variable if (oldIdp != null) { getBrokerRealm().identityProviders().get(name).update(idp); } else { Response ret = getBrokerRealm().identityProviders().create(idp); log.debug("Returned status from creating IDP: " + ret.getStatus()); if (ret.getStatus() != 201) { throw new IOException("Could not create IDP"); } } // Create the mappers for the IDP createIdpMappers(name, idpAtrMap, orgMrn); } private void createIdpMappers(String idpName, Map<String, String> idpAtrMap, String orgMrn) { String providerType = idpAtrMap.get("providerType"); IdentityProviderResource newIdpRes = getBrokerRealm().identityProviders().get(idpName); // Delete any existing mapper for (IdentityProviderMapperRepresentation mapper : newIdpRes.getMappers()) { newIdpRes.delete(mapper.getId()); } // Create mapper for hardcoded org value String orgMapperName = "org mapper"; IdentityProviderMapperRepresentation orgMapper = new IdentityProviderMapperRepresentation(); orgMapper.setIdentityProviderAlias(idpName); orgMapper.setIdentityProviderMapper("hardcoded-attribute-idp-mapper"); orgMapper.setName(orgMapperName); Map<String, String> orgMapperConf = new HashMap<>(); orgMapperConf.put("attribute.value", orgMrn); orgMapperConf.put("attribute", "org"); orgMapper.setConfig(orgMapperConf); newIdpRes.addMapper(orgMapper); // Create username mapper String usernameMapperName = "username mapper"; IdentityProviderMapperRepresentation usernameMapper = new IdentityProviderMapperRepresentation(); usernameMapper.setIdentityProviderAlias(idpName); usernameMapper.setName(usernameMapperName); Map<String, String> usernameMapperConf = new HashMap<>(); String mrnPrefix = MrnUtil.getMrnPrefix(orgMrn); if ("oidc".equals(providerType)) { // Create OIDC specific mapper usernameMapper.setIdentityProviderMapper("oidc-username-idp-mapper"); // Import username to an mrn in the form: urn:mrn:mcl:user:<org-id>:<user-id> usernameMapperConf.put("template", mrnPrefix + ":user:${ALIAS}:${CLAIM." + idpAtrMap.getOrDefault("usernameAttr", "preferred_username") + "}"); } else { usernameMapper.setIdentityProviderMapper("saml-username-idp-mapper"); // Import username to an mrn in the form: urn:mrn:mcl:user:<org-id>:<user-id> usernameMapperConf.put("template", mrnPrefix + ":user:${ALIAS}:${" + idpAtrMap.getOrDefault("usernameAttr", "NAMEID") + "}"); } usernameMapper.setConfig(usernameMapperConf); newIdpRes.addMapper(usernameMapper); // Add other mappers as needed // The mappers are set up differently based on the provider type Map<String, String> defaultMappers; String mapperConfKey; if ("oidc".equals(providerType)) { defaultMappers = oidcDefaultMappers; mapperConfKey = "claim"; } else { defaultMappers = samlDefaultMappers; mapperConfKey = "attribute.name"; } String mapperType = providerType + "-user-attribute-idp-mapper"; for (Map.Entry<String, String> entry : defaultMappers.entrySet()) { String attrName = attrNames2Keycloak.get(entry.getKey()); String attrValue = idpAtrMap.getOrDefault(entry.getKey(), entry.getValue()); // Skip creating this mapper if no value is defined if (attrValue == null) { continue; } String attrMapperName = attrName + " mapper"; IdentityProviderMapperRepresentation mapper = new IdentityProviderMapperRepresentation(); mapper.setIdentityProviderAlias(idpName); mapper.setIdentityProviderMapper(mapperType); mapper.setName(attrMapperName); Map<String, String> mapperConf = new HashMap<>(); mapperConf.put(mapperConfKey, attrValue); mapperConf.put("user.attribute", attrName); mapper.setConfig(mapperConf); newIdpRes.addMapper(mapper); } } /** * Delete Identity Provider with the given alias * * @param orgMrn MRN of the IDP to delete. */ public void deleteIdentityProvider(String orgMrn) { // First delete any users associated with the IDP. Find it by username, which is the mrn String alias = MrnUtil.getOrgShortNameFromOrgMrn(orgMrn); String searchStr = MrnUtil.getMrnPrefix(orgMrn) + ":user:" + alias + ":"; List<UserRepresentation> users = getBrokerRealm().users().search(/* username*/ searchStr, /* firstName */ null, /* lastName */ null, /* email */ null, /* first */ 0, /* max*/ 0); for (UserRepresentation user : users) { if (user.getUsername().startsWith(searchStr)) { getBrokerRealm().users().get(user.getId()).remove(); } } // Delete IDP getBrokerRealm().identityProviders().get(alias).remove(); } /** * Creates a user in keycloak. * * @param userMrn MRN of the user * @param firstName first name of user * @param lastName last name of user * @param password password of the user * @param email email of the user * @param orgMrn MRN of the org * @throws IOException */ public void createUser(String userMrn, String password, String firstName, String lastName, String email, String orgMrn, String permissions, boolean enabled) throws IOException, DuplicatedKeycloakEntry { log.debug("creating user: " + userMrn); UserRepresentation user = new UserRepresentation(); user.setUsername(email); user.setEnabled(enabled); if (email != null && !email.trim().isEmpty()) { user.setEmail(email); user.setEmailVerified(true); } if (firstName != null && !firstName.trim().isEmpty()) { user.setFirstName(firstName); } if (lastName != null && !lastName.trim().isEmpty()) { user.setLastName(lastName); } // Set attributes Map<String, List<String>> attr = new HashMap<>(); attr.put("org", Collections.singletonList(orgMrn)); attr.put("mrn", Collections.singletonList(userMrn)); if (permissions != null && !permissions.trim().isEmpty()) { attr.put("permissions", Collections.singletonList(permissions)); } user.setAttributes(attr); Response ret = getProjectUserRealm().users().create(user); String errMsg = ret.readEntity(String.class); if (ret.getStatus() != 201) { if (ret.getStatus() == 409) { log.debug("creating user failed due to duplicated user" + errMsg); throw new DuplicatedKeycloakEntry("User with mrn: " + userMrn + " already exists.", errMsg); } else { log.debug("creating user failed, status: " + ret.getStatus() + ", " + errMsg); throw new IOException("User creating failed: " + errMsg); } } log.debug("created user, status: " + ret.getStatus() + ", " + errMsg); ret.close(); // Set credentials CredentialRepresentation cred = new CredentialRepresentation(); cred.setType(CredentialRepresentation.PASSWORD); cred.setValue(password); // Make sure the user updates the password on first login cred.setTemporary(true); // Find the user by searching for the username user = getProjectUserRealm().users().search(email, null, null, null, -1, -1).get(0); user.setCredentials(Collections.singletonList(cred)); log.debug("setting password for user: " + user.getId()); getProjectUserRealm().users().get(user.getId()).resetPassword(cred); log.debug("created user"); } /** * Updates the user in keycloak * * @param userMrn MRN of the user * @param firstName first name of user * @param lastName last name of user * @param email email of the user * @throws IOException */ public void updateUser(String userMrn, String firstName, String lastName, String email, String newPermissions, boolean enabled) throws IOException { List<UserRepresentation> userReps = getProjectUserRealm().users().search(email, null, null, null, -1, -1); if (userReps.size() != 1) { log.debug( "Skipping user update! Found " + userReps.size() + " users while trying to update, expected 1"); throw new IOException( "User update failed! Found " + userReps.size() + " users while trying to update, expected 1"); } UserRepresentation user = userReps.get(0); boolean updated = false; if (email != null && !email.trim().isEmpty()) { user.setEmail(email); user.setEmailVerified(true); updated = true; } if (firstName != null && !firstName.trim().isEmpty()) { user.setFirstName(firstName); updated = true; } if (lastName != null && !lastName.trim().isEmpty()) { user.setLastName(lastName); updated = true; } Map<String, List<String>> attr = user.getAttributes(); if (attr.containsKey("permissions")) { List<String> oldPermissions = attr.get("permissions"); if (oldPermissions != null && !oldPermissions.isEmpty()) { String permission = oldPermissions.get(0); if (permission == null || !permission.equals(newPermissions)) { if (newPermissions != null) { attr.put("permissions", Collections.singletonList(newPermissions)); } else { attr.put("permissions", Collections.singletonList("")); } user.setAttributes(attr); updated = true; } } } else { if (newPermissions != null && !newPermissions.trim().isEmpty()) { attr.put("permissions", Collections.singletonList(newPermissions)); user.setAttributes(attr); updated = true; } } if (attr.containsKey("mrn")) { List<String> oldMrn = attr.get("mrn"); if (oldMrn != null && !oldMrn.isEmpty()) { String mrn = oldMrn.get(0); if (mrn == null || !mrn.equals(userMrn)) { attr.put("mrn", Collections.singletonList(userMrn)); user.setAttributes(attr); updated = true; } } } else { attr.put("mrn", Collections.singletonList(userMrn)); user.setAttributes(attr); updated = true; } if (updated) { getProjectUserRealm().users().get(user.getId()).update(user); } } /** * Delete a user from Keycloak * * @param email email of the user to delete */ public void deleteUser(String email) { // Find the user by searching for the username List<UserRepresentation> users = getProjectUserRealm().users().search(email, null, null, null, -1, -1); // If we found one, delete it if (!users.isEmpty()) { getProjectUserRealm().users().get(users.get(0).getId()).remove(); } } /** * Creates an OpenId Connect client in keycloak * * @param clientId The client id * @param type The client type, can be public, bearer-only or confidential * @param redirectUri The redirect uri * @return Returns the generated client secret, unless the type is public, in which case an empty string is returned. * @throws IOException */ public String createClient(String clientId, String type, String redirectUri) throws IOException, DuplicatedKeycloakEntry { ClientRepresentation client = new ClientRepresentation(); client.setClientId(clientId); client.setClientAuthenticatorType("client-secret"); if (redirectUri != null && !redirectUri.trim().isEmpty()) { client.setRedirectUris(Collections.singletonList(redirectUri)); } else { client.setRedirectUris(null); } client.setWebOrigins(Collections.singletonList("+")); client.setDirectAccessGrantsEnabled(false); client.setProtocol("openid-connect"); client.setEnabled(true); client.setConsentRequired(false); client.setClientTemplate(keycloakClientTemplate); // the template includes the mappers needed if ("public".equals(type)) { client.setBearerOnly(false); client.setPublicClient(true); } else if ("bearer-only".equals(type)) { client.setBearerOnly(true); client.setPublicClient(false); } else { // Fallback to "confidential" client.setBearerOnly(false); client.setPublicClient(false); } // Create the client Response ret = getBrokerRealm().clients().create(client); String errMsg = ret.readEntity(String.class); if (ret.getStatus() != 201) { if (ret.getStatus() == 409) { log.debug("creating client failed due to duplicated client" + errMsg); throw new DuplicatedKeycloakEntry("Client with mrn: " + clientId + " already exists.", errMsg); } else { log.debug("creating client failed, status: " + ret.getStatus() + ", " + errMsg); throw new IOException("Client creation failed: " + errMsg); } } if (!"public".equals(type)) { // The client secret can't be retrived by the ClientRepresentation (bug?), so we need to use the ClientResource ClientRepresentation createdClient = getBrokerRealm().clients().findByClientId(clientId).get(0); String secret = getBrokerRealm().clients().get(createdClient.getId()).getSecret().getValue(); return secret; } else { return ""; } } /** * Updates an OpenId Connect client in keycloak * * @param clientId * @param type * @param redirectUri * @return Returns the generated client secret, unless the type is public, in which case an empty string is returned. */ public String updateClient(String clientId, String type, String redirectUri) throws IOException { List<ClientRepresentation> clients = getBrokerRealm().clients().findByClientId(clientId); if (clients == null || clients.isEmpty()) { // hmm, this shouldn't happen... log.warn("Could not find client that should be upgraded - will create it!"); try { return this.createClient(clientId, type, redirectUri); } catch (DuplicatedKeycloakEntry duplicatedKeycloakEntry) { throw new IOException( "Client creation failed due to the client already existing, though it should not! "); } } ClientRepresentation client = clients.get(0); client.setClientAuthenticatorType(type); if (redirectUri != null && !redirectUri.trim().isEmpty()) { client.setRedirectUris(Collections.singletonList(redirectUri)); } else { client.setRedirectUris(null); } if ("public".equals(type)) { client.setBearerOnly(false); client.setPublicClient(true); } else if ("bearer-only".equals(type)) { client.setBearerOnly(true); client.setPublicClient(false); } else { // Fallback to "confidential" client.setBearerOnly(false); client.setPublicClient(false); } // Update client getBrokerRealm().clients().get(client.getId()).update(client); if (!type.equals("public")) { // The client secret can't be retrived by the ClientRepresentation (bug?), so we need to use the ClientResource String secret = getBrokerRealm().clients().get(client.getId()).getSecret().getValue(); return secret; } else { return ""; } } /** * Deletes an OpenId Connect client in keycloak * * @param clientId */ public void deleteClient(String clientId) { ClientRepresentation client = getBrokerRealm().clients().findByClientId(clientId).get(0); getBrokerRealm().clients().get(client.getId()).remove(); } /** * Gets the keycloak.json for this client. * * @param clientId client id/name * @return the keycloak json */ public String getClientKeycloakJson(String clientId) { ClientRepresentation client = getBrokerRealm().clients().findByClientId(clientId).get(0); String token = keycloakBrokerInstance.tokenManager().getAccessTokenString(); String url = keycloakBrokerBaseUrl + "admin/realms/" + keycloakBrokerRealm + "/clients/" + client.getId() + "/installation/providers/keycloak-oidc-keycloak-json"; return getFromKeycloak(url, token); } /** * Gets the keycloak.json for this client. * * @param clientId client id/name * @return the keycloak json */ public String getClientJbossXml(String clientId) { ClientRepresentation client = getBrokerRealm().clients().findByClientId(clientId).get(0); String token = keycloakBrokerInstance.tokenManager().getAccessTokenString(); String url = keycloakBrokerBaseUrl + "admin/realms/" + keycloakBrokerRealm + "/clients/" + client.getId() + "/installation/providers/keycloak-oidc-jboss-subsystem"; return getFromKeycloak(url, token); } /** * Helper function to GET from keycloak api that isn't supported by the client * * @param url The url to GET * @param token The access_token to use for identification * @return Returns a string representation of the result */ private String getFromKeycloak(String url, String token) { CloseableHttpClient client = HttpClientBuilder.create().build(); try { log.debug("get url: " + url); HttpGet get = new HttpGet(url); get.addHeader("Authorization", "Bearer " + token); try { HttpResponse response = client.execute(get); if (response.getStatusLine().getStatusCode() != 200) { log.debug("" + response.getStatusLine().getStatusCode()); return null; } String content = getContent(response.getEntity()); return content; } catch (IOException e) { throw new RuntimeException(e); } } finally { try { client.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } /** * Helper function to extract string from HttpEntity * * @param entity * @return * @throws IOException */ private static String getContent(HttpEntity entity) throws IOException { if (entity == null) return null; InputStream is = entity.getContent(); try { ByteArrayOutputStream os = new ByteArrayOutputStream(); int c; while ((c = is.read()) != -1) { os.write(c); } byte[] bytes = os.toByteArray(); String data = new String(bytes); return data; } finally { try { is.close(); } catch (IOException ignored) { } } } }