diff --git a/src/main/java/org/gcube/oidc/rest/OpenIdConnectRESTHelper.java b/src/main/java/org/gcube/oidc/rest/OpenIdConnectRESTHelper.java index 9b845dd..e25b2aa 100644 --- a/src/main/java/org/gcube/oidc/rest/OpenIdConnectRESTHelper.java +++ b/src/main/java/org/gcube/oidc/rest/OpenIdConnectRESTHelper.java @@ -71,6 +71,23 @@ public class OpenIdConnectRESTHelper { public static JWTToken queryClientToken(String clientId, String clientSecret, URL tokenURL) throws OpenIdConnectRESTHelperException { + return queryClientToken(clientId, clientSecret, tokenURL, null); + } + + /** + * Queries from the OIDC server an OIDC access token, by using provided clientId and client secret. + * + * @param clientId the client id + * @param clientSecret the client secret + * @param tokenUrl the token endpoint {@link URL} of the OIDC server + * @param extraHeaders extra HTTP headers to add to the request (e.g. X-D4Science-Context custom header), may be null + * @return the issued token + * @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details + */ + public static JWTToken queryClientToken(String clientId, String clientSecret, URL tokenURL, + Map extraHeaders) + throws OpenIdConnectRESTHelperException { + Map> params = new HashMap<>(); params.put("grant_type", Arrays.asList("client_credentials")); try { @@ -83,34 +100,41 @@ public class OpenIdConnectRESTHelper { } catch (UnsupportedEncodingException e) { logger.error("Cannot URL encode 'client_secret'", e); } - return performQueryTokenWithPOST(tokenURL, null, params); + return performQueryTokenWithPOST(tokenURL, null, params, extraHeaders); } public static JWTToken queryToken(String clientId, URL tokenURL, String code, String scope, String redirectURI) throws Exception { + return queryToken(clientId, tokenURL, code, scope, redirectURI, null); + } + + public static JWTToken queryToken(String clientId, URL tokenURL, String code, String scope, + String redirectURI, Map extraHeaders) throws Exception { + Map> 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"))); params.put("redirect_uri", Arrays.asList(URLEncoder.encode(redirectURI, "UTF-8"))); - return performQueryTokenWithPOST(tokenURL, null, params); + return performQueryTokenWithPOST(tokenURL, null, params, extraHeaders); } - protected static JWTToken performQueryTokenWithPOST(URL tokenURL, String authorization, Map> params) throws OpenIdConnectRESTHelperException { - return performQueryTokenWithPOST(tokenURL, authorization, params, null); + + return performQueryTokenWithPOST(tokenURL, authorization, params, null); } protected static JWTToken performQueryTokenWithPOST(URL tokenURL, String authorization, - Map> params, Map headers) throws OpenIdConnectRESTHelperException { + Map> params, Map headers) throws OpenIdConnectRESTHelperException { logger.debug("Querying access token from OIDC server with URL: {}", tokenURL); StringBuilder sb; try { - HttpURLConnection httpURLConnection = performURLEncodedPOSTSendData(tokenURL, params, authorization, headers); + HttpURLConnection httpURLConnection = performURLEncodedPOSTSendData(tokenURL, params, authorization, + headers); sb = new StringBuilder(); int httpResultCode = httpURLConnection.getResponseCode(); @@ -149,12 +173,13 @@ public class OpenIdConnectRESTHelper { } protected static HttpURLConnection performURLEncodedPOSTSendData(URL url, Map> params, - String authorization) throws IOException, ProtocolException, UnsupportedEncodingException { + String authorization) throws IOException, ProtocolException, UnsupportedEncodingException { return performURLEncodedPOSTSendData(url, params, authorization, null); } protected static HttpURLConnection performURLEncodedPOSTSendData(URL url, Map> params, - String authorization, Map headers) throws IOException, ProtocolException, UnsupportedEncodingException { + String authorization, Map headers) + throws IOException, ProtocolException, UnsupportedEncodingException { HttpURLConnection con = (HttpURLConnection) url.openConnection(); con.setRequestMethod("POST"); @@ -188,7 +213,7 @@ public class OpenIdConnectRESTHelper { * @param tokenUrl the token endpoint {@link URL} of the OIDC server * @param clientId the client id * @param clientSecret the client secret - * @param audience the audience (context) where to request the issuing of the ticket + * @param audience the audience (context) where to request the issuing of the token (URLEncoded or not) * @param permissions a list of permissions, can be null * @return the issued token * @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details @@ -196,74 +221,37 @@ public class OpenIdConnectRESTHelper { public static JWTToken queryUMAToken(URL tokenUrl, String clientId, String clientSecret, String audience, List permissions) throws OpenIdConnectRESTHelperException { - return queryUMAToken(tokenUrl, - "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()), - audience, permissions); + return queryUMAToken(tokenUrl, clientId, clientSecret, audience, permissions, null); } /** - * Queries from the OIDC server an exchanged token by using provided access token, for the given audience (context), - * in URLEncoded form or not, and optionally a list of permissions. + * Queries from the OIDC server an UMA token, by using provided clientId and client secret for the given audience + * (context), in URLEncoded form or not, and optionally a list of permissions. * * @param tokenUrl the token endpoint {@link URL} of the OIDC server - * @param authorization the auth token (the access token URLEncoded by the "Bearer " string) - * @param audience the audience (context) where to request the issuing of the ticket (URLEncoded) + * @param clientId the client id + * @param clientSecret the client secret + * @param audience the audience (context) where to request the issuing of the token (URLEncoded or not) * @param permissions a list of permissions, can be null + * @param extraHeaders extra HTTP headers to add to the request (e.g. X-D4Science-Context custom header), may be null * @return the issued token * @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details */ - public static JWTToken queryExchangeToken(URL tokenUrl, String authorization, String audience, String client_id, String client_secret, - List permissions) throws OpenIdConnectRESTHelperException { + public static JWTToken queryUMAToken(URL tokenUrl, String clientId, String clientSecret, String audience, + List permissions, Map extraHeaders) throws OpenIdConnectRESTHelperException { - logger.info("Queried exchangeToken for context " + audience); - - Map> params = new HashMap<>(); - - params.put("subject_token", Arrays.asList(authorization)); - params.put("client_id", Arrays.asList(client_id)); - params.put("client_secret", Arrays.asList(client_secret)); - params.put("grant_type", Arrays.asList("urn:ietf:params:oauth:grant-type:token-exchange")); - params.put("subject_token_type", Arrays.asList("urn:ietf:params:oauth:token-type:access_token")); - params.put("requested_token_type", Arrays.asList("urn:ietf:params:oauth:token-type:access_token")); - - if (audience.startsWith("/")) { - try { - logger.trace("Audience was provided in non URL encoded form, encoding it"); - audience = URLEncoder.encode(audience, "UTF-8"); - } catch (UnsupportedEncodingException e) { - logger.error("Cannot URL encode 'audience'", e); - } - } - try { - params.put("audience", Arrays.asList(URLEncoder.encode(audience, "UTF-8"))); - } catch (UnsupportedEncodingException e) { - logger.error("Cannot URL encode 'audience'", e); - } - 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())); - } - - - // Map headers = new HashMap<>(); - // headers.put("X-D4Science-Context", audience); - - return performQueryTokenWithPOST(tokenUrl, null, params/*, headers*/); + return queryUMAToken(tokenUrl, + "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()), + audience, permissions, extraHeaders); } - + /** * Queries from the OIDC server an UMA token, by using provided access token, for the given audience (context), * in URLEncoded form or not, and optionally a list of permissions. * * @param tokenUrl the token endpoint {@link URL} of the OIDC server * @param authorization the auth token (the access token URLEncoded by the "Bearer " string) - * @param audience the audience (context) where to request the issuing of the ticket (URLEncoded) + * @param audience the audience (context) where to request the issuing of the token (URLEncoded or not) * @param permissions a list of permissions, can be null * @return the issued token * @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details @@ -271,6 +259,24 @@ public class OpenIdConnectRESTHelper { public static JWTToken queryUMAToken(URL tokenUrl, String authorization, String audience, List permissions) throws OpenIdConnectRESTHelperException { + return queryUMAToken(tokenUrl, authorization, audience, permissions, null); + } + + /** + * Queries from the OIDC server an UMA token, by using provided access token, for the given audience (context), + * in URLEncoded form or not, and optionally a list of permissions. + * + * @param tokenUrl the token endpoint {@link URL} of the OIDC server + * @param authorization the auth token (the access token URLEncoded by the "Bearer " string) + * @param audience the audience (context) where to request the issuing of the token (URLEncoded or not) + * @param permissions a list of permissions, can be null + * @param extraHeaders extra HTTP headers to add to the request (e.g. X-D4Science-Context custom header), may be null + * @return the issued token + * @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details + */ + public static JWTToken queryUMAToken(URL tokenUrl, String authorization, String audience, + List permissions, Map extraHeaders) throws OpenIdConnectRESTHelperException { + Map> params = new HashMap<>(); params.put("grant_type", Arrays.asList("urn:ietf:params:oauth:grant-type:uma-ticket")); if (audience.startsWith("/")) { @@ -296,7 +302,104 @@ public class OpenIdConnectRESTHelper { } }).collect(Collectors.toList())); } - return performQueryTokenWithPOST(tokenUrl, authorization, params); + return performQueryTokenWithPOST(tokenUrl, authorization, params, extraHeaders); + } + + /** + * Queries from the OIDC server an exchanged token by using provided access token, optionally for the given audience (context) + * in URLEncoded form or not. + * + * @param tokenUrl the token endpoint {@link URL} of the OIDC server + * @param authorization the auth token (the access token URLEncoded by the "Bearer " string) + * @param audience the audience (context) where to request the issuing of the token (URLEncoded or not), may be null + * @param clientId the client id + * @param clientSecret the client secret + * @param extraHeaders extra HTTP headers to add to the request (e.g. X-D4Science-Context custom header), may be null + * @return the issued token + * @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details + */ + public static JWTToken queryExchangeToken(URL tokenUrl, String authorization, String audience, String client_id, + String client_secret, Map extraHeaders) throws OpenIdConnectRESTHelperException { + + return queryExchangeToken(tokenUrl, authorization, audience, client_id, client_secret, + "urn:ietf:params:oauth:token-type:access_token", null, extraHeaders); + } + + /** + * Queries from the OIDC server an exchanged token by using provided access token, optionally for the given audience (context) + * in URLEncoded form or not. + * + * @param tokenUrl the token endpoint {@link URL} of the OIDC server + * @param authorization the auth token (the access token URLEncoded by the "Bearer " string) + * @param audience the audience (context) where to request the issuing of the token (URLEncoded or not), may be null + * @param clientId the client id + * @param clientSecret the client secret + * @param withRefreshToken request also the refresh token (forced to true for offline requests) + * @param offline request a refresh token of offline type (TYP claim) + * @param extraHeaders extra HTTP headers to add to the request (e.g. X-D4Science-Context custom header), may be null + * @return the issued token + * @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details + */ + public static JWTToken queryExchangeToken(URL tokenUrl, String authorization, String audience, String clientId, + String clientSecret, boolean withRefreshToken, boolean offline, Map extraHeaders) + throws OpenIdConnectRESTHelperException { + + return queryExchangeToken(tokenUrl, authorization, audience, clientId, clientSecret, + withRefreshToken || offline ? "urn:ietf:params:oauth:token-type:refresh_token" + : "urn:ietf:params:oauth:token-type:access_token", + offline ? "offline_access" : null, extraHeaders); + } + + /** + * Queries from the OIDC server an exchanged token by using provided access token, optionally for the given audience (context) + * in URLEncoded form or not. + * + * @param tokenUrl the token endpoint {@link URL} of the OIDC server + * @param authorization the auth token (the access token URLEncoded by the "Bearer " string) + * @param audience the audience (context) where to request the issuing of the token (URLEncoded or not), may be null + * @param clientId the client id + * @param clientSecret the client secret + * @param requestedTokenType the requested token type (e.g. urn:ietf:params:oauth:token-type:refresh_token for refresh token) + * @param scope the optional scope to request (e.g. offline_access for an offline token) + * @param extraHeaders extra HTTP headers to add to the request (e.g. X-D4Science-Context custom header), may be null + * @return the issued token + * @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details + */ + public static JWTToken queryExchangeToken(URL tokenUrl, String authorization, String audience, String clientId, + String clientSecret, String requestedTokenType, String scope, Map extraHeaders) + throws OpenIdConnectRESTHelperException { + + logger.info("Querying exchange token for context: " + audience); + + Map> params = new HashMap<>(); + + params.put("subject_token", Arrays.asList(authorization)); + params.put("client_id", Arrays.asList(clientId)); + params.put("client_secret", Arrays.asList(clientSecret)); + params.put("grant_type", Arrays.asList("urn:ietf:params:oauth:grant-type:token-exchange")); + params.put("subject_token_type", Arrays.asList("urn:ietf:params:oauth:token-type:access_token")); + params.put("requested_token_type", Arrays.asList(requestedTokenType)); + if (scope != null) { + params.put("scope", Arrays.asList(scope)); + } + + if (audience != null) { + if (audience.startsWith("/")) { + try { + logger.trace("Audience was provided in non URL encoded form, encoding it"); + audience = URLEncoder.encode(audience, "UTF-8"); + } catch (UnsupportedEncodingException e) { + logger.error("Cannot URL encode 'audience'", e); + } + } + try { + params.put("audience", Arrays.asList(URLEncoder.encode(audience, "UTF-8"))); + } catch (UnsupportedEncodingException e) { + logger.error("Cannot URL encode 'audience'", e); + } + } + + return performQueryTokenWithPOST(tokenUrl, null, params, extraHeaders); } /** @@ -322,6 +425,7 @@ public class OpenIdConnectRESTHelper { */ public static JWTToken refreshToken(URL tokenURL, String clientId, JWTToken token) throws OpenIdConnectRESTHelperException { + return refreshToken(tokenURL, clientId, null, token); }