Removed `permission` list from exchange method signature and improved it adding also `refresh-token` as requested token type and `offline_access` scope. Added also extra headers support for all the token methods.

This commit is contained in:
Mauro Mugnaini 2024-03-27 18:43:21 +01:00
parent 6da3a9f55a
commit 098bfc9fef
Signed by: mauro.mugnaini
GPG Key ID: 2440CFD0EB321EA8
1 changed files with 165 additions and 61 deletions

View File

@ -71,6 +71,23 @@ public class OpenIdConnectRESTHelper {
public static JWTToken queryClientToken(String clientId, String clientSecret, URL tokenURL) public static JWTToken queryClientToken(String clientId, String clientSecret, URL tokenURL)
throws OpenIdConnectRESTHelperException { 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. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @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<String, String> extraHeaders)
throws OpenIdConnectRESTHelperException {
Map<String, List<String>> params = new HashMap<>(); Map<String, List<String>> params = new HashMap<>();
params.put("grant_type", Arrays.asList("client_credentials")); params.put("grant_type", Arrays.asList("client_credentials"));
try { try {
@ -83,24 +100,30 @@ public class OpenIdConnectRESTHelper {
} catch (UnsupportedEncodingException e) { } catch (UnsupportedEncodingException e) {
logger.error("Cannot URL encode 'client_secret'", 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, public static JWTToken queryToken(String clientId, URL tokenURL, String code, String scope,
String redirectURI) throws Exception { 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<String, String> extraHeaders) throws Exception {
Map<String, List<String>> params = new HashMap<>(); Map<String, List<String>> params = new HashMap<>();
params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8"))); params.put("client_id", Arrays.asList(URLEncoder.encode(clientId, "UTF-8")));
params.put("grant_type", Arrays.asList("authorization_code")); params.put("grant_type", Arrays.asList("authorization_code"));
params.put("scope", Arrays.asList(URLEncoder.encode(scope, "UTF-8"))); params.put("scope", Arrays.asList(URLEncoder.encode(scope, "UTF-8")));
params.put("code", Arrays.asList(URLEncoder.encode(code, "UTF-8"))); params.put("code", Arrays.asList(URLEncoder.encode(code, "UTF-8")));
params.put("redirect_uri", Arrays.asList(URLEncoder.encode(redirectURI, "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, protected static JWTToken performQueryTokenWithPOST(URL tokenURL, String authorization,
Map<String, List<String>> params) throws OpenIdConnectRESTHelperException { Map<String, List<String>> params) throws OpenIdConnectRESTHelperException {
return performQueryTokenWithPOST(tokenURL, authorization, params, null); return performQueryTokenWithPOST(tokenURL, authorization, params, null);
} }
@ -110,7 +133,8 @@ public class OpenIdConnectRESTHelper {
logger.debug("Querying access token from OIDC server with URL: {}", tokenURL); logger.debug("Querying access token from OIDC server with URL: {}", tokenURL);
StringBuilder sb; StringBuilder sb;
try { try {
HttpURLConnection httpURLConnection = performURLEncodedPOSTSendData(tokenURL, params, authorization, headers); HttpURLConnection httpURLConnection = performURLEncodedPOSTSendData(tokenURL, params, authorization,
headers);
sb = new StringBuilder(); sb = new StringBuilder();
int httpResultCode = httpURLConnection.getResponseCode(); int httpResultCode = httpURLConnection.getResponseCode();
@ -154,7 +178,8 @@ public class OpenIdConnectRESTHelper {
} }
protected static HttpURLConnection performURLEncodedPOSTSendData(URL url, Map<String, List<String>> params, protected static HttpURLConnection performURLEncodedPOSTSendData(URL url, Map<String, List<String>> params,
String authorization, Map<String, String> headers) throws IOException, ProtocolException, UnsupportedEncodingException { String authorization, Map<String, String> headers)
throws IOException, ProtocolException, UnsupportedEncodingException {
HttpURLConnection con = (HttpURLConnection) url.openConnection(); HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST"); con.setRequestMethod("POST");
@ -188,7 +213,7 @@ public class OpenIdConnectRESTHelper {
* @param tokenUrl the token endpoint {@link URL} of the OIDC server * @param tokenUrl the token endpoint {@link URL} of the OIDC server
* @param clientId the client id * @param clientId the client id
* @param clientSecret the client secret * @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 <code>null</code> * @param permissions a list of permissions, can be <code>null</code>
* @return the issued token * @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details * @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details
@ -196,65 +221,28 @@ public class OpenIdConnectRESTHelper {
public static JWTToken queryUMAToken(URL tokenUrl, String clientId, String clientSecret, String audience, public static JWTToken queryUMAToken(URL tokenUrl, String clientId, String clientSecret, String audience,
List<String> permissions) throws OpenIdConnectRESTHelperException { List<String> permissions) throws OpenIdConnectRESTHelperException {
return queryUMAToken(tokenUrl, return queryUMAToken(tokenUrl, clientId, clientSecret, audience, permissions, null);
"Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()),
audience, permissions);
} }
/** /**
* Queries from the OIDC server an exchanged token by using provided access token, for the given audience (context), * Queries from the OIDC server an UMA token, by using provided clientId and client secret for the given audience
* in URLEncoded form or not, and optionally a list of permissions. * (context), in URLEncoded form or not, and optionally a list of permissions.
* *
* @param tokenUrl the token endpoint {@link URL} of the OIDC server * @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 clientId the client id
* @param audience the audience (context) where to request the issuing of the ticket (URLEncoded) * @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 <code>null</code> * @param permissions a list of permissions, can be <code>null</code>
* @param extraHeaders extra HTTP headers to add to the request (e.g. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @return the issued token * @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details * @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, public static JWTToken queryUMAToken(URL tokenUrl, String clientId, String clientSecret, String audience,
List<String> permissions) throws OpenIdConnectRESTHelperException { List<String> permissions, Map<String, String> extraHeaders) throws OpenIdConnectRESTHelperException {
logger.info("Queried exchangeToken for context " + audience); return queryUMAToken(tokenUrl,
"Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes()),
Map<String, List<String>> params = new HashMap<>(); audience, permissions, extraHeaders);
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<String, String> headers = new HashMap<>();
// headers.put("X-D4Science-Context", audience);
return performQueryTokenWithPOST(tokenUrl, null, params/*, headers*/);
} }
/** /**
@ -263,7 +251,7 @@ public class OpenIdConnectRESTHelper {
* *
* @param tokenUrl the token endpoint {@link URL} of the OIDC server * @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 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 <code>null</code> * @param permissions a list of permissions, can be <code>null</code>
* @return the issued token * @return the issued token
* @throws OpenIdConnectRESTHelperException if an error occurs (also an unauthorized call), inspect the exception for details * @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, public static JWTToken queryUMAToken(URL tokenUrl, String authorization, String audience,
List<String> permissions) throws OpenIdConnectRESTHelperException { List<String> 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 <code>null</code>
* @param extraHeaders extra HTTP headers to add to the request (e.g. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @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<String> permissions, Map<String, String> extraHeaders) throws OpenIdConnectRESTHelperException {
Map<String, List<String>> params = new HashMap<>(); Map<String, List<String>> params = new HashMap<>();
params.put("grant_type", Arrays.asList("urn:ietf:params:oauth:grant-type:uma-ticket")); params.put("grant_type", Arrays.asList("urn:ietf:params:oauth:grant-type:uma-ticket"));
if (audience.startsWith("/")) { if (audience.startsWith("/")) {
@ -296,7 +302,104 @@ public class OpenIdConnectRESTHelper {
} }
}).collect(Collectors.toList())); }).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 <code>null</code>
* @param clientId the client id
* @param clientSecret the client secret
* @param extraHeaders extra HTTP headers to add to the request (e.g. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @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<String, String> 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 <code>null</code>
* @param clientId the client id
* @param clientSecret the client secret
* @param withRefreshToken request also the refresh token (forced to <code>true</code> 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. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @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<String, String> 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 <code>null</code>
* @param clientId the client id
* @param clientSecret the client secret
* @param requestedTokenType the requested token type (e.g. <code>urn:ietf:params:oauth:token-type:refresh_token</code> for refresh token)
* @param scope the optional scope to request (e.g. <code>offline_access</code> for an offline token)
* @param extraHeaders extra HTTP headers to add to the request (e.g. <code>X-D4Science-Context</code> custom header), may be <code>null</code>
* @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<String, String> extraHeaders)
throws OpenIdConnectRESTHelperException {
logger.info("Querying exchange token for context: " + audience);
Map<String, List<String>> 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) public static JWTToken refreshToken(URL tokenURL, String clientId, JWTToken token)
throws OpenIdConnectRESTHelperException { throws OpenIdConnectRESTHelperException {
return refreshToken(tokenURL, clientId, null, token); return refreshToken(tokenURL, clientId, null, token);
} }