2020-06-18 10:56:20 +02:00
|
|
|
package org.gcube.oidc.rest;
|
2020-05-21 15:47:28 +02:00
|
|
|
|
|
|
|
import java.io.BufferedReader;
|
2020-08-12 20:12:21 +02:00
|
|
|
import java.io.ByteArrayOutputStream;
|
2020-05-21 15:47:28 +02:00
|
|
|
import java.io.IOException;
|
2020-08-12 20:12:21 +02:00
|
|
|
import java.io.InputStream;
|
2020-05-21 15:47:28 +02:00
|
|
|
import java.io.InputStreamReader;
|
|
|
|
import java.io.OutputStream;
|
|
|
|
import java.io.UnsupportedEncodingException;
|
|
|
|
import java.net.HttpURLConnection;
|
2020-06-29 16:22:51 +02:00
|
|
|
import java.net.ProtocolException;
|
2020-05-21 15:47:28 +02:00
|
|
|
import java.net.URL;
|
|
|
|
import java.net.URLEncoder;
|
|
|
|
import java.util.Arrays;
|
|
|
|
import java.util.HashMap;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.Map;
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
import org.slf4j.Logger;
|
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
|
|
|
|
public class OpenIdConnectRESTHelper {
|
|
|
|
|
|
|
|
protected static final Logger logger = LoggerFactory.getLogger(OpenIdConnectRESTHelper.class);
|
|
|
|
|
2020-06-18 10:56:20 +02:00
|
|
|
public static String buildLoginRequestURL(URL loginURL, String clientId, String state, String redirectURI)
|
2020-05-21 15:47:28 +02:00
|
|
|
throws UnsupportedEncodingException {
|
|
|
|
|
|
|
|
Map<String, List<String>> params = new HashMap<String, List<String>>();
|
|
|
|
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
|
|
|
|
params.put("response_type", Arrays.asList("code"));
|
|
|
|
params.put("scope", Arrays.asList("openid"));
|
|
|
|
params.put("state", Arrays.asList(URLEncoder.encode(state, "UTF-8")));
|
2020-06-18 10:56:20 +02:00
|
|
|
params.put("redirect_uri", Arrays.asList(URLEncoder.encode(redirectURI, "UTF-8")));
|
2020-05-21 15:47:28 +02:00
|
|
|
params.put("login", Arrays.asList("true"));
|
|
|
|
String q = mapToQueryString(params);
|
2020-06-18 10:56:20 +02:00
|
|
|
return loginURL + "?" + q;
|
2020-05-21 15:47:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public static String mapToQueryString(Map<String, List<String>> params) {
|
|
|
|
String q = params.entrySet().stream().flatMap(p -> p.getValue().stream().map(v -> p.getKey() + "=" + v))
|
|
|
|
.reduce((p1, p2) -> p1 + "&" + p2).orElse("");
|
|
|
|
|
2020-07-03 17:31:19 +02:00
|
|
|
logger.debug("Query string is: {}", q);
|
2020-05-21 15:47:28 +02:00
|
|
|
return q;
|
|
|
|
}
|
|
|
|
|
2020-06-18 10:56:20 +02:00
|
|
|
public static JWTToken queryClientToken(String clientId, String clientSecret, URL tokenURL) throws Exception {
|
2020-06-04 16:12:50 +02:00
|
|
|
Map<String, List<String>> params = new HashMap<>();
|
|
|
|
params.put("grant_type", Arrays.asList("client_credentials"));
|
|
|
|
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
|
|
|
|
params.put("client_secret", Arrays.asList(URLEncoder.encode(clientSecret, "UTF-8")));
|
2020-06-18 10:56:20 +02:00
|
|
|
return performQueryTokenWithPOST(tokenURL, null, params);
|
2020-06-04 16:12:50 +02:00
|
|
|
}
|
|
|
|
|
2020-06-18 10:56:20 +02:00
|
|
|
public static JWTToken queryToken(String clientId, URL tokenURL, String code, String scope,
|
|
|
|
String redirectURI) throws Exception {
|
2020-05-21 15:47:28 +02:00
|
|
|
|
|
|
|
Map<String, List<String>> params = new HashMap<>();
|
|
|
|
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
|
|
|
|
params.put("grant_type", Arrays.asList("authorization_code"));
|
|
|
|
params.put("scope", Arrays.asList(URLEncoder.encode(scope, "UTF-8")));
|
|
|
|
params.put("code", Arrays.asList(URLEncoder.encode(code, "UTF-8")));
|
2020-06-18 10:56:20 +02:00
|
|
|
params.put("redirect_uri", Arrays.asList(URLEncoder.encode(redirectURI, "UTF-8")));
|
|
|
|
return performQueryTokenWithPOST(tokenURL, null, params);
|
2020-05-21 15:47:28 +02:00
|
|
|
}
|
|
|
|
|
2020-06-18 10:56:20 +02:00
|
|
|
public static JWTToken performQueryTokenWithPOST(URL tokenURL, String authorization,
|
2020-05-21 15:47:28 +02:00
|
|
|
Map<String, List<String>> params)
|
|
|
|
throws Exception {
|
|
|
|
|
2020-07-03 17:31:19 +02:00
|
|
|
logger.debug("Querying access token from OIDC server with URL: {}", tokenURL);
|
2020-06-29 16:22:51 +02:00
|
|
|
HttpURLConnection httpURLConnection = performURLEncodedPOSTSendData(tokenURL, params, authorization);
|
2020-05-21 15:47:28 +02:00
|
|
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
2020-06-29 16:22:51 +02:00
|
|
|
int httpResultCode = httpURLConnection.getResponseCode();
|
2020-07-03 17:31:19 +02:00
|
|
|
logger.trace("HTTP Response code: {}", httpResultCode);
|
2020-06-18 10:56:20 +02:00
|
|
|
if (httpResultCode != HttpURLConnection.HTTP_OK) {
|
2020-06-29 16:22:51 +02:00
|
|
|
BufferedReader br = new BufferedReader(new InputStreamReader(httpURLConnection.getErrorStream(), "UTF-8"));
|
2020-05-21 15:47:28 +02:00
|
|
|
String line = null;
|
|
|
|
while ((line = br.readLine()) != null) {
|
|
|
|
sb.append(line + "\n");
|
|
|
|
}
|
|
|
|
br.close();
|
|
|
|
throw new Exception("Unable to get token " + sb);
|
|
|
|
} else {
|
2020-06-29 16:22:51 +02:00
|
|
|
BufferedReader br = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(), "UTF-8"));
|
2020-05-21 15:47:28 +02:00
|
|
|
String line = null;
|
|
|
|
while ((line = br.readLine()) != null) {
|
|
|
|
sb.append(line + "\n");
|
|
|
|
}
|
|
|
|
br.close();
|
|
|
|
}
|
|
|
|
return JWTToken.fromString(sb.toString());
|
|
|
|
}
|
|
|
|
|
2020-08-12 20:12:21 +02:00
|
|
|
protected static HttpURLConnection performURLEncodedPOSTSendData(URL url, Map<String, List<String>> params,
|
2020-06-29 16:22:51 +02:00
|
|
|
String authorization) throws IOException, ProtocolException, UnsupportedEncodingException {
|
|
|
|
|
2020-08-12 20:12:21 +02:00
|
|
|
HttpURLConnection con = (HttpURLConnection) url.openConnection();
|
2020-06-29 16:22:51 +02:00
|
|
|
con.setRequestMethod("POST");
|
|
|
|
con.setDoOutput(true);
|
|
|
|
con.setDoInput(true);
|
|
|
|
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
|
|
|
|
con.setRequestProperty("Accept", "application/json");
|
|
|
|
if (authorization != null) {
|
2020-07-03 17:31:19 +02:00
|
|
|
logger.debug("Adding authorization header as: {}", authorization);
|
2020-06-29 16:22:51 +02:00
|
|
|
con.setRequestProperty("Authorization", authorization);
|
|
|
|
}
|
|
|
|
OutputStream os = con.getOutputStream();
|
2020-06-30 13:57:41 +02:00
|
|
|
|
|
|
|
String queryString = mapToQueryString(params);
|
2020-07-03 17:31:19 +02:00
|
|
|
logger.debug("Parameters query string is: {}", queryString);
|
2020-06-30 13:57:41 +02:00
|
|
|
os.write(queryString.getBytes("UTF-8"));
|
2020-06-29 16:22:51 +02:00
|
|
|
os.close();
|
|
|
|
return con;
|
|
|
|
}
|
|
|
|
|
2020-06-18 10:56:20 +02:00
|
|
|
public static JWTToken queryUMAToken(URL tokenUrl, String authorizationToken, String audience,
|
2020-05-21 15:47:28 +02:00
|
|
|
List<String> permissions) throws Exception {
|
|
|
|
|
|
|
|
Map<String, List<String>> params = new HashMap<>();
|
|
|
|
params.put("grant_type", Arrays.asList("urn:ietf:params:oauth:grant-type:uma-ticket"));
|
|
|
|
params.put("audience", Arrays.asList(URLEncoder.encode(audience, "UTF-8")));
|
|
|
|
if (permissions != null && !permissions.isEmpty()) {
|
|
|
|
params.put(
|
|
|
|
"permission", permissions.stream().map(s -> {
|
|
|
|
try {
|
|
|
|
return URLEncoder.encode(s, "UTF-8");
|
|
|
|
} catch (UnsupportedEncodingException e) {
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}).collect(Collectors.toList()));
|
|
|
|
}
|
|
|
|
return performQueryTokenWithPOST(tokenUrl, authorizationToken, params);
|
|
|
|
}
|
|
|
|
|
2020-06-29 16:22:51 +02:00
|
|
|
public static JWTToken refreshToken(URL tokenURL, JWTToken token) throws Exception {
|
|
|
|
return refreshToken(tokenURL, null, null, token);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static JWTToken refreshToken(URL tokenURL, String clientId, JWTToken token) throws Exception {
|
|
|
|
return refreshToken(tokenURL, clientId, null, token);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static JWTToken refreshToken(URL tokenURL, String clientId, String clientSecret, JWTToken token)
|
|
|
|
throws Exception {
|
|
|
|
|
2020-05-21 15:47:28 +02:00
|
|
|
Map<String, List<String>> params = new HashMap<>();
|
2020-06-29 16:22:51 +02:00
|
|
|
params.put("grant_type", Arrays.asList("refresh_token"));
|
|
|
|
if (clientId == null) {
|
2020-06-30 13:57:41 +02:00
|
|
|
clientId = getClientIdFromToken(token);
|
2020-06-29 16:22:51 +02:00
|
|
|
}
|
|
|
|
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
|
|
|
|
if (clientSecret != null) {
|
|
|
|
params.put("client_secret", Arrays.asList(URLEncoder.encode(clientSecret, "UTF-8")));
|
|
|
|
}
|
|
|
|
params.put("refresh_token", Arrays.asList(token.getRefreshTokenString()));
|
|
|
|
return performQueryTokenWithPOST(tokenURL, null, params);
|
|
|
|
}
|
|
|
|
|
2020-06-30 13:57:41 +02:00
|
|
|
protected static String getClientIdFromToken(JWTToken token) {
|
|
|
|
String clientId;
|
2020-07-03 17:31:19 +02:00
|
|
|
logger.debug("Client id not provided, using authorized party field (azp)");
|
2020-06-30 13:57:41 +02:00
|
|
|
clientId = token.getAzp();
|
|
|
|
if (clientId == null) {
|
2020-07-03 17:31:19 +02:00
|
|
|
logger.debug("Authorized party field (azp) not present, getting one of the audience field (aud)");
|
2020-06-30 13:57:41 +02:00
|
|
|
clientId = getFirstAudienceNoAccount(token);
|
|
|
|
}
|
|
|
|
return clientId;
|
|
|
|
}
|
|
|
|
|
2020-06-29 16:22:51 +02:00
|
|
|
private static String getFirstAudienceNoAccount(JWTToken token) {
|
|
|
|
// Trying to get it from the token's audience ('aud' field), getting the first except the 'account'
|
|
|
|
List<String> tokenAud = token.getAud();
|
|
|
|
tokenAud.remove(JWTToken.ACCOUNT_RESOURCE);
|
|
|
|
if (tokenAud.size() > 0) {
|
|
|
|
return tokenAud.iterator().next();
|
|
|
|
} else {
|
|
|
|
// Setting it to empty string to avoid NPE in encoding
|
|
|
|
return "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean logout(URL logoutUrl, JWTToken token) throws IOException {
|
|
|
|
return logout(logoutUrl, null, token);
|
|
|
|
}
|
|
|
|
|
|
|
|
public static boolean logout(URL logoutUrl, String clientId, JWTToken token) throws IOException {
|
|
|
|
Map<String, List<String>> params = new HashMap<>();
|
|
|
|
if (clientId == null) {
|
2020-06-30 13:57:41 +02:00
|
|
|
clientId = getClientIdFromToken(token);
|
2020-06-29 16:22:51 +02:00
|
|
|
}
|
2020-06-18 10:56:20 +02:00
|
|
|
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
|
2020-05-21 15:47:28 +02:00
|
|
|
params.put("refresh_token", Arrays.asList(token.getRefreshTokenString()));
|
2020-06-18 10:56:20 +02:00
|
|
|
logger.info("Performing logut from OIDC server with URL: " + logoutUrl);
|
2020-08-12 20:12:21 +02:00
|
|
|
HttpURLConnection httpURLConnection = performURLEncodedPOSTSendData(logoutUrl, params, token.getAccessTokenAsBearer());
|
2020-06-29 16:22:51 +02:00
|
|
|
int responseCode = httpURLConnection.getResponseCode();
|
2020-05-21 15:47:28 +02:00
|
|
|
if (responseCode == 204) {
|
|
|
|
logger.info("Logout performed correctly");
|
2020-06-29 16:22:51 +02:00
|
|
|
return true;
|
2020-05-21 15:47:28 +02:00
|
|
|
} else {
|
2020-07-03 17:31:19 +02:00
|
|
|
logger.error("Cannot perfrom logout: [{}] {}", responseCode, httpURLConnection.getResponseMessage());
|
2020-05-21 15:47:28 +02:00
|
|
|
}
|
2020-06-29 16:22:51 +02:00
|
|
|
return false;
|
2020-05-21 15:47:28 +02:00
|
|
|
}
|
2020-08-12 20:12:21 +02:00
|
|
|
|
2020-08-13 20:47:37 +02:00
|
|
|
public static byte[] getUserAvatar(URL avatarURL, JWTToken token) {
|
|
|
|
logger.debug("Getting user avatar from URL: {}", avatarURL);
|
|
|
|
ByteArrayOutputStream buffer;
|
|
|
|
try {
|
|
|
|
HttpURLConnection conn = (HttpURLConnection) avatarURL.openConnection();
|
|
|
|
conn.setRequestMethod("GET");
|
|
|
|
conn.setDoOutput(false);
|
|
|
|
conn.setDoInput(true);
|
|
|
|
conn.setRequestProperty("Accept", "image/png, image/jpeg, image/gif");
|
|
|
|
if (token != null) {
|
|
|
|
String authorization = token.getAccessTokenAsBearer();
|
|
|
|
logger.debug("Adding authorization header as: {}", authorization);
|
|
|
|
conn.setRequestProperty("Authorization", authorization);
|
|
|
|
}
|
|
|
|
logger.debug("Getting the resource opening the stream");
|
|
|
|
InputStream is = conn.getInputStream();
|
|
|
|
buffer = new ByteArrayOutputStream();
|
|
|
|
int nRead;
|
|
|
|
byte[] data = new byte[1024];
|
|
|
|
while ((nRead = is.read(data, 0, data.length)) != -1) {
|
|
|
|
buffer.write(data, 0, nRead);
|
|
|
|
}
|
2020-08-12 20:12:21 +02:00
|
|
|
|
2020-08-13 20:47:37 +02:00
|
|
|
buffer.flush();
|
|
|
|
return buffer.toByteArray();
|
|
|
|
} catch (IOException e) {
|
|
|
|
logger.debug("Can't download avatar", e);
|
|
|
|
}
|
|
|
|
return null;
|
2020-08-12 20:12:21 +02:00
|
|
|
}
|
2020-05-21 15:47:28 +02:00
|
|
|
}
|