diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17dda65..2222206 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,15 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
# Changelog for "oidc-library"
+## [v1.3.2-SNAPSHOT]
+Token exchange (#27099)
+Header X-D4Science-Context in query exchange and refresh
+
+- performQueryTokenWithPOST accepts also optional headers
+- performURLEncodedPOSTSendData accepts also optional headers
+- implemented queryExchangeToken
+
+
## [v1.3.1]
Added `Catalogue-Manager` and `Catalogue-Moderator` roles to the enum (#23623)
diff --git a/pom.xml b/pom.xml
index 4eb3e49..97039cc 100644
--- a/pom.xml
+++ b/pom.xml
@@ -13,7 +13,7 @@
org.gcube.common
oidc-library
- 1.3.1
+ 1.3.2-SNAPSHOT
diff --git a/src/main/java/org/gcube/oidc/rest/OpenIdConnectRESTHelper.java b/src/main/java/org/gcube/oidc/rest/OpenIdConnectRESTHelper.java
index 12ea16e..9b845dd 100644
--- a/src/main/java/org/gcube/oidc/rest/OpenIdConnectRESTHelper.java
+++ b/src/main/java/org/gcube/oidc/rest/OpenIdConnectRESTHelper.java
@@ -98,13 +98,19 @@ public class OpenIdConnectRESTHelper {
return performQueryTokenWithPOST(tokenURL, null, params);
}
+
protected static JWTToken performQueryTokenWithPOST(URL tokenURL, String authorization,
Map> params) throws OpenIdConnectRESTHelperException {
+ return performQueryTokenWithPOST(tokenURL, authorization, params, null);
+ }
+
+ protected static JWTToken performQueryTokenWithPOST(URL tokenURL, String authorization,
+ 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);
+ HttpURLConnection httpURLConnection = performURLEncodedPOSTSendData(tokenURL, params, authorization, headers);
sb = new StringBuilder();
int httpResultCode = httpURLConnection.getResponseCode();
@@ -143,7 +149,12 @@ 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 {
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setRequestMethod("POST");
@@ -155,6 +166,12 @@ public class OpenIdConnectRESTHelper {
logger.debug("Adding authorization header as: {}", authorization);
con.setRequestProperty("Authorization", authorization);
}
+
+ if (headers != null) {
+ for (String key : headers.keySet()) {
+ con.setRequestProperty(key, headers.get(key));
+ }
+ }
OutputStream os = con.getOutputStream();
String queryString = mapToQueryString(params);
@@ -184,6 +201,62 @@ public class OpenIdConnectRESTHelper {
audience, permissions);
}
+ /**
+ * 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.
+ *
+ * @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 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
+ */
+ public static JWTToken queryExchangeToken(URL tokenUrl, String authorization, String audience, String client_id, String client_secret,
+ List permissions) 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*/);
+ }
+
/**
* 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.