diff --git a/CHANGELOG.md b/CHANGELOG.md index ea1aa73..d283b52 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Changelog -## [v1.2.0] - 2023-03-20 +## [v1.3.0-SNAPSHOT] - 20240-03-22 +Token exchange (#27099) + +## [v1..0] - 2023-03-20 - Decode Button - Updatet layout diff --git a/deploy.sh b/deploy.sh index cde1d2c..ad2f121 100755 --- a/deploy.sh +++ b/deploy.sh @@ -1,2 +1,3 @@ +VERSION=1.3.0-SNAPSHOT mvn package -scp target/rpt-token-portlet-1.2.0.war life@10.1.30.156:/home/life/Portal-Bundle/deploy/rpt-token-portlet.war +scp target/rpt-token-portlet-$VERSION.war life@10.1.30.156:/home/life/Portal-Bundle/deploy/rpt-token-portlet.war diff --git a/pom.xml b/pom.xml index bc0fc5e..13559b6 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ rpt-token-portlet war RPT UMA Token Portlet - 1.2.0 + 1.3.0-SNAPSHOT Requesting Party Token Portlet diff --git a/src/main/java/org/gcube/portlets/admin/OpenIdConnectRESTHelperExtended.java b/src/main/java/org/gcube/portlets/admin/OpenIdConnectRESTHelperExtended.java new file mode 100644 index 0000000..418d698 --- /dev/null +++ b/src/main/java/org/gcube/portlets/admin/OpenIdConnectRESTHelperExtended.java @@ -0,0 +1,73 @@ +package org.gcube.portlets.admin; + +import java.io.UnsupportedEncodingException; +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.gcube.oidc.rest.JWTToken; +import org.gcube.oidc.rest.OpenIdConnectRESTHelper; +import org.gcube.oidc.rest.OpenIdConnectRESTHelperException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class OpenIdConnectRESTHelperExtended extends OpenIdConnectRESTHelper { + protected static final Logger logger = LoggerFactory.getLogger(OpenIdConnectRESTHelperExtended.class); + + + /** + * 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())); + } + + return performQueryTokenWithPOST(tokenUrl, null, params); + } +} diff --git a/src/main/java/org/gcube/portlets/admin/RPTTokenReader.java b/src/main/java/org/gcube/portlets/admin/RPTTokenReader.java index 5b7a296..f5be1f8 100644 --- a/src/main/java/org/gcube/portlets/admin/RPTTokenReader.java +++ b/src/main/java/org/gcube/portlets/admin/RPTTokenReader.java @@ -2,6 +2,7 @@ package org.gcube.portlets.admin; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; @@ -21,6 +22,7 @@ import org.gcube.common.scope.impl.ScopeBean.Type; import org.gcube.oidc.rest.JWTToken; import org.gcube.oidc.rest.OpenIdConnectConfiguration; import org.gcube.oidc.rest.OpenIdConnectRESTHelper; +//import org.gcube.oidc.rest.OpenIdConnectRESTHelper; import org.gcube.oidc.rest.OpenIdConnectRESTHelperException; import org.gcube.portal.oidc.lr62.JWTCacheProxy; import org.gcube.portal.oidc.lr62.LiferayOpenIdConnectConfiguration; @@ -44,44 +46,59 @@ public class RPTTokenReader extends MVCPortlet { private static com.liferay.portal.kernel.log.Log log = LogFactoryUtil.getLog(RPTTokenReader.class); @Override - public void render(RenderRequest renderRequest, RenderResponse renderResponse) throws PortletException, IOException { + public void render(RenderRequest renderRequest, RenderResponse renderResponse) + throws PortletException, IOException { GroupManager gm = new LiferayGroupManager(); - try { + try { User theUser = PortalUtil.getUser(renderRequest); String currentContext = getCurrentContext(renderRequest); ScopeBean bean = new ScopeBean(currentContext); List userContexts = new ArrayList(); + List vreContexts = new ArrayList(); List userGroups = gm.listGroupsByUser(theUser.getUserId()); if (bean.is(Type.VRE)) { userContexts.add(currentContext); - } - else { + vreContexts.add(currentContext); + } else { for (GCubeGroup g : userGroups) { - if(! (g.getFriendlyURL().equals("/guest") || g.getFriendlyURL().equals("/global") )) {// skipping these sites + // skipping these sites + if (!(g.getFriendlyURL().equals("/guest") || g.getFriendlyURL().equals("/global"))) { if (g.getGroupName().equals(PortalContext.getConfiguration().getInfrastructureName())) { String context = gm.getInfrastructureScope(g.getGroupId()); userContexts.add(context); + if (context.split("/").length == 4){ + vreContexts.add(context); + } } if (g.getParentGroupId() > 0) { String context = gm.getInfrastructureScope(g.getGroupId()); userContexts.add(context); + if (context.split("/").length == 4){ + vreContexts.add(context); + } } } } } renderRequest.setAttribute("userGroups", userGroups); renderRequest.setAttribute("userContexts", userContexts); + renderRequest.setAttribute("vreContexts", vreContexts); } catch (Exception e) { e.printStackTrace(); } - super.render(renderRequest, renderResponse); + super.render(renderRequest, renderResponse); } - public void serveResource(ResourceRequest resourceRequest, ResourceResponse resourceResponse) throws IOException, PortletException { + public void serveResource(ResourceRequest resourceRequest, ResourceResponse resourceResponse) + throws IOException, PortletException { String context = ParamUtil.getString(resourceRequest, "context", null); - System.out.println("Selected context="+context); - HttpServletRequest httpReq = PortalUtil.getOriginalServletRequest(PortalUtil.getHttpServletRequest(resourceRequest)); + System.out.println("Selected context=" + context); + HttpServletRequest httpReq = PortalUtil + .getOriginalServletRequest(PortalUtil.getHttpServletRequest(resourceRequest)); JWTToken umaToken = null; + JWTToken exchangedToken = null; + + GroupManager gm = new LiferayGroupManager(); resourceResponse.setContentType("application/json"); @@ -90,10 +107,10 @@ public class RPTTokenReader extends MVCPortlet { try { User theUser = PortalUtil.getUser(resourceRequest); OpenIdConnectConfiguration configuration = LiferayOpenIdConnectConfiguration.getConfiguration(httpReq); - + jsonObject.put("token_url", configuration.getTokenURL().toString()); JWTCacheProxy jwtCacheProxy = JWTCacheProxy.getInstance(); - String sessionId = httpReq.getSession().getId(); + String sessionId = httpReq.getSession().getId(); String urlEncodedContext = null; try { urlEncodedContext = URLEncoder.encode(context, "UTF-8"); @@ -101,55 +118,78 @@ public class RPTTokenReader extends MVCPortlet { // Almost impossible log.error("Cannot URL encode context", e); } + JWTToken authToken = jwtCacheProxy.getOIDCToken(theUser, sessionId); - umaToken = OpenIdConnectRESTHelper.queryUMAToken(configuration.getTokenURL(), authToken.getAccessTokenAsBearer(), urlEncodedContext, null); - log.debug("Got a new UMA token " + umaToken.getTokenEssentials()); + umaToken = OpenIdConnectRESTHelper.queryUMAToken(configuration.getTokenURL(), + authToken.getAccessTokenAsBearer(), urlEncodedContext, null); + + // URL auth_url = configuration.getTokenURL(); + // log.info("auth_url " + auth_url); + // log.info("authToken " + authToken.getAccessTokenString()); + // log.info("umaToken " + umaToken.getAccessTokenString()); + // log.info("context " + context); + // log.info("encoded_context " + urlEncodedContext); + // log.info("client_id " + configuration.getPortalClientId()); + // log.info("client_secret " + configuration.getPortalClientSecret()); + + exchangedToken = OpenIdConnectRESTHelperExtended.queryExchangeToken( + configuration.getTokenURL(), + umaToken.getAccessTokenString(), + urlEncodedContext, + configuration.getPortalClientId(), + configuration.getPortalClientSecret(), + null); + + + // log.info("exchangedToken " + exchangedToken.getAccessTokenString()); + + // log.debug("Got a new UMA token " + exchangedToken.getTokenEssentials()); } catch (OpenIdConnectRESTHelperException e) { resourceResponse.setProperty(ResourceResponse.HTTP_STATUS_CODE, "" + e.getStatus()); - + e.printStackTrace(); jsonObject.put("success", false); jsonObject.put("comment", e.getMessage()); - resourceResponse.getWriter().println(jsonObject); + resourceResponse.getWriter().println(jsonObject); super.serveResource(resourceRequest, resourceResponse); return; } catch (Exception e) { e.printStackTrace(); jsonObject.put("success", false); jsonObject.put("comment", e.getMessage()); - resourceResponse.getWriter().println(jsonObject); + resourceResponse.getWriter().println(jsonObject); super.serveResource(resourceRequest, resourceResponse); return; } // } catch (Exception e) { - // e.printStackTrace(); - // JSONObject jsonObject = JSONFactoryUtil.createJSONObject(); - // jsonObject.put("success", false); - // jsonObject.put("comment", e.getMessage()); - // resourceResponse.getWriter().println(jsonObject); - // super.serveResource(resourceRequest, resourceResponse); - // } + // e.printStackTrace(); + // JSONObject jsonObject = JSONFactoryUtil.createJSONObject(); + // jsonObject.put("success", false); + // jsonObject.put("comment", e.getMessage()); + // resourceResponse.getWriter().println(jsonObject); + // super.serveResource(resourceRequest, resourceResponse); + // } jsonObject.put("success", true); - jsonObject.put("access_token", umaToken.getAccessTokenString()); - jsonObject.put("refresh_token", umaToken.getRefreshTokenString()); + jsonObject.put("access_token", exchangedToken.getAccessTokenString()); + jsonObject.put("refresh_token", exchangedToken.getRefreshTokenString()); - jsonObject.put("raw_token", umaToken.getRaw()); - - jsonObject.put("access_token_exp", umaToken.getExp()); - jsonObject.put("essential", umaToken.getTokenEssentials()); + jsonObject.put("raw_token", exchangedToken.getRaw()); + + jsonObject.put("access_token_exp", exchangedToken.getExp()); + jsonObject.put("essential", exchangedToken.getTokenEssentials()); jsonObject.put("client_id", umaToken.getAzp()); - JSONArray audiences = JSONFactoryUtil.createJSONArray(); - List list_audiences = umaToken.getAud(); + JSONArray audiences = JSONFactoryUtil.createJSONArray(); + List list_audiences = exchangedToken.getAud(); for (int i = 0; i < list_audiences.size(); i++) { audiences.put((String) list_audiences.get(i)); } jsonObject.put("audience", audiences); - resourceResponse.getWriter().println(jsonObject); + resourceResponse.getWriter().println(jsonObject); super.serveResource(resourceRequest, resourceResponse); } @@ -164,11 +204,10 @@ public class RPTTokenReader extends MVCPortlet { return null; } - - private String getCurrentContext(long groupId) { + private String getCurrentContext(long groupId) { try { - PortalContext pContext = PortalContext.getConfiguration(); - return pContext.getCurrentScope(""+groupId); + PortalContext pContext = PortalContext.getConfiguration(); + return pContext.getCurrentScope("" + groupId); } catch (Exception e) { e.printStackTrace(); } diff --git a/src/main/webapp/html/rpttokenreader/view.jsp b/src/main/webapp/html/rpttokenreader/view.jsp index d7d3f5a..d4a87ca 100644 --- a/src/main/webapp/html/rpttokenreader/view.jsp +++ b/src/main/webapp/html/rpttokenreader/view.jsp @@ -7,15 +7,16 @@ <% List userGroups = (List) request.getAttribute("userGroups"); pageContext.setAttribute("userGroups", userGroups); -List userContexts = (List) request.getAttribute("userContexts"); -pageContext.setAttribute("userContexts", userContexts); +List user_contexts = (List) request.getAttribute("vreContexts"); +pageContext.setAttribute("user_contexts", user_contexts); String sub = "-"; -if (userContexts != null && userContexts.size()>0){ - sub = userContexts.get(0).substring(userContexts.get(0).lastIndexOf("/") + 1); +if (user_contexts != null && user_contexts.size()>0){ + sub = user_contexts.get(0).substring(user_contexts.get(0).lastIndexOf("/") + 1); } + pageContext.setAttribute("firstContextName", sub); %> @@ -76,33 +77,35 @@ pageContext.setAttribute("firstContextName", sub); global_token_response = resultObject; $('#tokenResult').text(resultObject.access_token); - $('#refreshTokenResult').text(resultObject.refresh_token); - - - $('#refresh_token_url').text(resultObject.token_url); - $('#client_id').text(resultObject.client_id); - - $('#usage_token').html(generateAccessCurl("[ACCESS_TOKEN]", "[SERVICE_URL]")) - $('#usage_refresh').html(generateRefreshCurl("[CLIENT_ID]", "[REFRESH_TOKEN]", "[REFRESH_URL]")) var raw = JSON.parse(resultObject.raw_token) - var expires_in = raw.expires_in; if (expires_in) { $('#tokenResultDetails').html('expires in ' + formatDuration(expires_in) + ''); } + $('#usage_token').html(generateAccessCurl("[ACCESS_TOKEN]", "[SERVICE_URL]")); + resetAccessUrl(); - var refresh_expires_in = raw.refresh_expires_in; - if (expires_in) { - $('#refreshTokenResultDetails').html('expires in ' + formatDuration(refresh_expires_in) + ''); + openInfo('token_info'); + + var refresh_token = resultObject.refresh_token; + if (refresh_token) { + $('#refreshTokenResult').text(resultObject.refresh_token); + $('#refresh_token_url').text(resultObject.token_url); + $('#client_id').text(resultObject.client_id); + $('#usage_refresh').html(generateRefreshCurl("[CLIENT_ID]", "[REFRESH_TOKEN]", "[REFRESH_URL]")) + + var refresh_expires_in = raw.refresh_expires_in; + if (refresh_expires_in) { + $('#refreshTokenResultDetails').html('expires in ' + formatDuration(refresh_expires_in) + ''); + } + $('#response_group_refreshToken').show(); + $('#refresh_parameters').show(); + } else { + $('#response_group_refreshToken').hide(); + $('#refresh_parameters').hide(); } - var not_before_policy = raw["not-before-policy"]; - - console.log("expires_in", expires_in); - console.log("refresh_expires_in", refresh_expires_in); - console.log("not-before-policy", not_before_policy); - $('#rawTokenResult').html(JSON.stringify(raw, undefined, 4)); $('#response_group').show(); @@ -114,7 +117,9 @@ pageContext.setAttribute("firstContextName", sub); } function setError(error_comment) { - $('response_group').hide(); + $('#response_group').hide(); + $('#intro_container').hide(); + $('#error_msg').show(); $('#error_msg').html(error_comment); @@ -179,16 +184,11 @@ pageContext.setAttribute("firstContextName", sub); '_blank' ); } - - - function generateAccessCurl(token, service_url) { - return "curl -X GET -H 'Authorization: Bearer " + token + "' \\\n" - + "\t " + service_url + return "curl -X GET -H 'Authorization: Bearer " + token + "' " + service_url } - function generateRefreshCurl(client_id, refresh_token, refresh_url) { return "curl -X POST --location '" + refresh_url + "' \\\n" + // "\t--header 'Content-Type: application/x-www-form-urlencoded' \\\n" + @@ -221,28 +221,48 @@ pageContext.setAttribute("firstContextName", sub); }); } - function copyAccessCurl() { - + function defaultServicePathUrl() { // ***.dev.d4science.org => api.dev.d4science.org // ***.pre.d4science.org => api.pre.d4science.org // xxx.d4science.org => api.d4science.org - var client_id = global_token_response.client_id; - var service_path = "api.d4science.org"; - if (client_id.endsWith("dev.d4science.org")){ + var service_path = "api.d4science.org"; + var client_id = global_token_response.client_id; + + if (client_id.endsWith("dev.d4science.org")) { service_path = "api.dev.d4science.org"; - } else if (client_id.endsWith("pre.d4science.org")){ + } else if (client_id.endsWith("pre.d4science.org")) { service_path = "api.pre.d4science.org"; } + console.log(defaultServicePathUrl, client_id, service_path); + return service_path; + } + + function defaultServiceAccessUrl() { + var service_path = defaultServicePathUrl(); + + //return "https://" + service_path + "/catalogue/items"; + return "https://" + service_path + "/rest/2/people/profile"; + } + + function copyAccessCurl() { + var service_access_url = $("#input_access_url").val(); + var curl = generateAccessCurl( global_token_response.access_token, - "https://" + service_path + "/catalogue/items"); + service_access_url); + + console.info("curl", curl); copyText(curl); } - + function resetAccessUrl() { + var accessUrl = defaultServiceAccessUrl(); + console.log("accessUrl", accessUrl) + $("#input_access_url").val(accessUrl); + } function copyRefreshCurl() { var curl = generateRefreshCurl( @@ -267,24 +287,24 @@ pageContext.setAttribute("firstContextName", sub); - +
Configuration error, no context available
- -

Current context: ${firstContextName}

- + +

Current context: ${firstContextName}

+
- +

Select the context:

@@ -293,7 +313,7 @@ pageContext.setAttribute("firstContextName", sub);
- + @@ -311,9 +331,9 @@ pageContext.setAttribute("firstContextName", sub);
OAuth Access Token
(Bearer Authorization)
- @@ -333,27 +353,40 @@ pageContext.setAttribute("firstContextName", sub);
@@ -388,7 +421,8 @@ pageContext.setAttribute("firstContextName", sub);
- - +
+ You can decode the tokens using https://jwt.io/ +