keycloak-client/src/main/java/org/gcube/common/keycloak/DefaultKeycloakClient.java

162 lines
6.8 KiB
Java

package org.gcube.common.keycloak;
import static org.gcube.resources.discovery.icclient.ICFactory.clientFor;
import static org.gcube.resources.discovery.icclient.ICFactory.queryFor;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.gcube.common.gxrest.request.GXHTTPStringRequest;
import org.gcube.common.gxrest.response.inbound.GXInboundResponse;
import org.gcube.common.keycloak.model.TokenResponse;
import org.gcube.common.resources.gcore.ServiceEndpoint;
import org.gcube.common.resources.gcore.ServiceEndpoint.AccessPoint;
import org.gcube.common.scope.api.ScopeProvider;
import org.gcube.resources.discovery.client.api.DiscoveryClient;
import org.gcube.resources.discovery.client.queries.api.SimpleQuery;
public class DefaultKeycloakClient implements KeycloakClient {
private static final String PERMISSION_PARAMETER = "permission";
private static final String GRANT_TYPE_PARAMETER = "grant_type";
private static final String UMA_TOKEN_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:uma-ticket";
private static final String AUDIENCE_PARAMETER = "audience";
@Override
public URL findTokenEndpointURL() throws KeycloakClientException {
logger.debug("Creating simple query");
SimpleQuery query = queryFor(ServiceEndpoint.class);
query.addCondition(
String.format("$resource/Profile/Category/text() eq '%s'", CATEGORY))
.addCondition(String.format("$resource/Profile/Name/text() eq '%s'", NAME))
.setResult(String.format("$resource/Profile/AccessPoint[Description/text() eq '%s']", DESCRIPTION));
logger.debug("Creating client for AccessPoint");
DiscoveryClient<AccessPoint> client = clientFor(AccessPoint.class);
logger.trace("Submitting query: {}", query);
List<AccessPoint> accessPoints = client.submit(query);
if (accessPoints.size() == 0) {
throw new KeycloakClientException("Service endpoint not found");
} else if (accessPoints.size() > 1) {
throw new KeycloakClientException("Found more than one endpoint with query");
}
String address = accessPoints.iterator().next().address();
logger.debug("Found address: {}", address);
try {
return new URL(address);
} catch (MalformedURLException e) {
throw new KeycloakClientException("Cannot create URL from address: " + address, e);
}
}
@Override
public TokenResponse queryUMAToken(String clientId, String clientSecret, List<String> permissions)
throws KeycloakClientException {
return queryUMAToken(clientId, clientSecret, ScopeProvider.instance.get(), permissions);
}
@Override
public TokenResponse queryUMAToken(String clientId, String clientSecret, String audience,
List<String> permissions) throws KeycloakClientException {
return queryUMAToken(findTokenEndpointURL(), clientId, clientSecret, audience, permissions);
}
@Override
public TokenResponse queryUMAToken(URL tokenURL, String clientId, String clientSecret, String audience,
List<String> permissions) throws KeycloakClientException {
return queryUMAToken(tokenURL,
"Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()),
audience, permissions);
}
@Override
public TokenResponse queryUMAToken(URL tokenURL, String authorization, String audience,
List<String> permissions) throws KeycloakClientException {
logger.debug("Querying token from Keycloak server with URL: {}", tokenURL);
Map<String, List<String>> params = new HashMap<>();
params.put(GRANT_TYPE_PARAMETER, Arrays.asList(UMA_TOKEN_GRANT_TYPE));
try {
params.put(AUDIENCE_PARAMETER, Arrays.asList(URLEncoder.encode(checkAudience(audience), "UTF-8")));
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'audience'", e);
}
if (permissions != null && !permissions.isEmpty()) {
params.put(
PERMISSION_PARAMETER, permissions.stream().map(s -> {
try {
return URLEncoder.encode(s, "UTF-8");
} catch (UnsupportedEncodingException e) {
return "";
}
}).collect(Collectors.toList()));
}
// Constructing request object
GXHTTPStringRequest request;
try {
String queryString = params.entrySet().stream()
.flatMap(p -> p.getValue().stream().map(v -> p.getKey() + "=" + v))
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
request = GXHTTPStringRequest.newRequest(tokenURL.toString())
.header("Content-Type", "application/x-www-form-urlencoded").withBody(queryString);
request.isExternalCall(true);
if (authorization != null) {
logger.debug("Adding authorization header as: {}", authorization);
request = request.header("Authorization", authorization);
}
} catch (Exception e) {
throw new KeycloakClientException("Cannot construct the request object correctly", e);
}
GXInboundResponse response;
try {
response = request.post();
} catch (Exception e) {
throw new KeycloakClientException("Cannot send request correctly", e);
}
if (response.isSuccessResponse()) {
try {
return response.tryConvertStreamedContentFromJson(TokenResponse.class);
} catch (Exception e) {
throw new KeycloakClientException("Cannot construct token response object correctly", e);
}
} else {
throw KeycloakClientException.create("Unable to get token", response.getHTTPCode(),
response.getHeaderFields()
.getOrDefault("Content-Type", Collections.singletonList("unknown/unknown")).get(0),
response.getMessage());
}
}
private static String checkAudience(String audience) {
if (audience.startsWith("/")) {
try {
logger.trace("Audience was provided in non URL encoded form, encoding it");
return URLEncoder.encode(audience, "UTF-8");
} catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'audience'", e);
}
}
return audience;
}
}