diff --git a/src/main/java/org/gcube/portal/oidc/lr62/OIDCUmaUtil.java b/src/main/java/org/gcube/portal/oidc/lr62/OIDCUmaUtil.java index 4d5c672..e63dfda 100644 --- a/src/main/java/org/gcube/portal/oidc/lr62/OIDCUmaUtil.java +++ b/src/main/java/org/gcube/portal/oidc/lr62/OIDCUmaUtil.java @@ -1,20 +1,36 @@ package org.gcube.portal.oidc.lr62; +import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.URL; import java.net.URLEncoder; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + import org.gcube.common.authorization.library.provider.UmaJWTProvider; import org.gcube.oidc.rest.JWTToken; import org.gcube.oidc.rest.OpenIdConnectConfiguration; import org.gcube.oidc.rest.OpenIdConnectRESTHelper; +import org.gcube.oidc.rest.OpenIdConnectRESTHelperException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.liferay.portal.model.User; + public class OIDCUmaUtil { private static final Logger log = LoggerFactory.getLogger(OIDCUmaUtil.class); + private static final boolean REFRESH_UMA_TOKEN = false; + private static final String LOGOUT_URI = "/c/portal/logout"; + private static final boolean FORCE_LOGOUT_ON_INVALID_OIDC = true; + private static final boolean FORCE_LOGOUT_ON_MISSING_OIDC = true; + private static final boolean FORCE_LOGOUT_ON_OIDC_REFRESH_ERROR = true; + private static final int MAX_AUTHORIZATION_RETRY_ATTEMPTS = 4; + private static final int AUTHORIZATION_RETRY_ATTEMPTS_DELAY = 4000; + public static void provideConfiguredPortalClientUMATokenInThreadLocal(String infraContext) { OpenIdConnectConfiguration configuration = LiferayOpenIdConnectConfiguration.getConfiguration(); String clientId = configuration.getPortalClientId(); @@ -59,4 +75,223 @@ public class OIDCUmaUtil { return; } } + + + public static void checkUMATicketAndProvideInThreadLocal(HttpServletRequest request, HttpServletResponse response, + User user, HttpSession session, String scope) { + + if (user == null) { + log.error("Current user not found, cannot continue"); + return; + } + log.debug("Current user is: {} [{}]", user.getScreenName(), user.getEmailAddress()); + + if (session == null) { + log.debug("Session is null, cannot continue"); + return; + } + String sessionId = session.getId(); + log.debug("Current session ID is {}", sessionId); + + String urlEncodedScope = null; + try { + urlEncodedScope = URLEncoder.encode(scope, "UTF-8"); + } catch (UnsupportedEncodingException e) { + // Almost impossible + log.error("Cannot URL encode scope", e); + return; + } + log.debug("URL encoded scope is: {}", urlEncodedScope); + + JWTToken umaToken = null; + synchronized (JWTCacheProxy.getInstance().getMutexFor(user)) { + log.trace("Getting UMA token for user {}, and session {}", user.getScreenName(), sessionId); + umaToken = JWTCacheProxy.getInstance().getUMAToken(user, sessionId); + if (umaToken != null && !umaToken.isExpired() && umaToken.getAud().contains(urlEncodedScope)) { + log.trace("Current UMA token is OK {}", umaToken.getTokenEssentials()); + } else { + if (umaToken != null && umaToken.getAud().contains(urlEncodedScope) && umaToken.isExpired()) { + if (REFRESH_UMA_TOKEN) { + log.debug("Suitable UMA token found but is expired, trying to refresh it {}", + umaToken.getTokenEssentials()); + + OpenIdConnectConfiguration configuration = LiferayOpenIdConnectConfiguration + .getConfiguration(request); + try { + umaToken = OpenIdConnectRESTHelper.refreshToken(configuration.getTokenURL(), umaToken); + log.debug("Got a refreshed UMA token {}", umaToken.getTokenEssentials()); + + log.debug("Setting the refreshed UMA token in cache proxy for user {}, and session]", + user.getScreenName(), sessionId); + + JWTCacheProxy.getInstance().setUMAToken(user, sessionId, umaToken); + } catch (OpenIdConnectRESTHelperException e) { + if (e.hasJSONPayload()) { + if (OpenIdConnectRESTHelper.isInvalidBearerTokenError(e.getResponseString())) { + if (FORCE_LOGOUT_ON_INVALID_OIDC) { + log.warn("OIDC token is become invalid, forcing redirect to logout URI"); + forceLogout(response); + } else { + log.warn("OIDC token is become invalid, cannot continue"); + } + return; + } else if (OpenIdConnectRESTHelper.isTokenNotActiveError(e.getResponseString())) { + log.info("UMA token is no more active, get new one"); + } else { + log.error("Other UMA token refresh error", e); + } + } else { + log.error("Refreshing UMA token on server " + umaToken.getTokenEssentials(), e); + } + umaToken = null; + log.debug( + "Removing inactive UMA token from cache proxy if present for user {} and session {}", + user.getScreenName(), sessionId); + + JWTCacheProxy.getInstance().removeUMAToken(user, sessionId); + } + } else { + log.debug("Suitable UMA token found but it is expired." + + "It will be replaced with new one according to settings {}", + umaToken.getTokenEssentials()); + + umaToken = null; + log.debug("Removing inactive UMA token from cache proxy if present for user {} and session {}", + user.getScreenName(), sessionId); + + JWTCacheProxy.getInstance().removeUMAToken(user, sessionId); + } + } + if (umaToken == null || !umaToken.getAud().contains(urlEncodedScope)) { + boolean scopeIsChanged = false; + if (umaToken == null) { + log.debug("Getting new UMA token for scope {}", urlEncodedScope); + } else if (!umaToken.getAud().contains(urlEncodedScope)) { + scopeIsChanged = true; + log.info("Getting new UMA token for scope {} since it has been issued for another scope {}", + urlEncodedScope, umaToken.getTokenEssentials()); + } + log.debug("Getting OIDC token from cache proxy for user {} and session {}", user.getScreenName(), + sessionId); + + JWTToken authToken = JWTCacheProxy.getInstance().getOIDCToken(user, sessionId); + if (authToken == null) { + if (FORCE_LOGOUT_ON_MISSING_OIDC) { + log.debug("OIDC token is null in cache proxy, force redirecting to logut URI"); + forceLogout(response); + } else { + log.debug("OIDC token is null in cache proxy, cannot continue!"); + } + return; + } else { + log.debug("OIDC token is {}", authToken.getTokenEssentials()); + } + OpenIdConnectConfiguration configuration = LiferayOpenIdConnectConfiguration + .getConfiguration(request); + + boolean isNotAuthorized = false; + int authorizationAttempts = 0; + do { + try { + if (isNotAuthorized || scopeIsChanged || authToken.isExpired()) { + if (isNotAuthorized) { + log.info( + "UMA token is not authorized with current OIDC token, " + + "refreshing it to be sure that new grants are present. " + + "[attempts: {}]", + authorizationAttempts); + + // Resetting the flag to be sure to have correct log message each loop + isNotAuthorized = false; + } else if (scopeIsChanged) { + log.info( + "Scope is changed, refreshing token to be sure that new grants are present"); + } else if (authToken.isExpired()) { + log.debug("OIDC token is expired, trying to refresh it {}", + authToken.getTokenEssentials()); + } + try { + authToken = OpenIdConnectRESTHelper.refreshToken(configuration.getTokenURL(), + authToken); + } catch (OpenIdConnectRESTHelperException e) { + if (FORCE_LOGOUT_ON_OIDC_REFRESH_ERROR) { + log.warn("Error refreshing OIDC token, force redirecting to logut URI"); + forceLogout(response); + } else { + log.error("Refreshing OIDC token on server", e); + } + return; + } + log.debug("Setting refreshed OIDC token in cache proxy"); + JWTCacheProxy.getInstance().setOIDCToken(user, sessionId, authToken); + } + log.info("Getting UMA token from OIDC endpoint for scope: " + urlEncodedScope); + umaToken = OpenIdConnectRESTHelper.queryUMAToken(configuration.getTokenURL(), + authToken.getAccessTokenAsBearer(), urlEncodedScope, null); + + log.debug("Got new UMA token {}", umaToken.getTokenEssentials()); + } catch (OpenIdConnectRESTHelperException e) { + if (e.hasJSONPayload()) { + if (OpenIdConnectRESTHelper.isInvalidBearerTokenError(e.getResponseString())) { + if (FORCE_LOGOUT_ON_INVALID_OIDC) { + log.warn("OIDC token is become invalid, forcing redirect to logout URI"); + forceLogout(response); + } else { + log.error("OIDC token is become invalid, cannot continue"); + } + return; + } else if (OpenIdConnectRESTHelper + .isAccessDeniedNotAuthorizedError(e.getResponseString())) { + + log.info("UMA token is" + (isNotAuthorized ? " still" : "") + + " not authorized with actual OIDC token"); + + isNotAuthorized = true; + authorizationAttempts += 1; + if (authorizationAttempts <= MAX_AUTHORIZATION_RETRY_ATTEMPTS) { + log.debug("Sleeping " + AUTHORIZATION_RETRY_ATTEMPTS_DELAY + + " ms and looping refreshing the OIDC"); + try { + Thread.sleep(AUTHORIZATION_RETRY_ATTEMPTS_DELAY); + } catch (InterruptedException ie) { + ie.printStackTrace(); + } + } else { + log.warn("OIDC token refresh attempts exhausted"); + return; + } + } + } else { + log.error("Getting UMA token from server", e); + return; + } + } + } while (isNotAuthorized); + } + + log.debug("Setting UMA token in cache proxy for user {} and session {}", user.getScreenName(), + sessionId); + + JWTCacheProxy.getInstance().setUMAToken(user, sessionId, umaToken); + } + } + + log.trace("Current UMA token in use is: {}", umaToken.getTokenEssentials()); + + log.debug("Setting UMA token with jti {} in UMA JWT provider", umaToken.getJti()); + UmaJWTProvider.instance.set(umaToken.getRaw()); + } + + protected static void forceLogout(HttpServletResponse response) { + try { + if (!response.isCommitted()) { + response.sendRedirect(LOGOUT_URI); + } else { + log.warn("Cannot redirect to logout URI since the response is already commited"); + } + } catch (IOException e) { + log.error("Cannot redirect to logout URI: " + LOGOUT_URI, e); + } + } + }