package org.gcube.common.security.secrets; import java.util.Base64; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; import org.gcube.common.keycloak.KeycloakClientFactory; import org.gcube.common.keycloak.model.AccessToken; import org.gcube.common.keycloak.model.ModelUtils; import org.gcube.common.keycloak.model.RefreshToken; import org.gcube.common.keycloak.model.TokenResponse; import org.gcube.common.keycloak.model.util.Time; import org.gcube.common.security.Caller; import org.gcube.common.security.GCubeJWTObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import providers.RenewalProvider; /** * @author Luca Frosini (ISTI - CNR) */ public class JWTSecret extends Secret { private static final Logger logger = LoggerFactory.getLogger(JWTSecret.class); /** * The interval of time expressed in milliseconds used as guard to refresh the token before that it expires . * TimeUnit has been used to in place of just * using the number to have a clearer code */ public static final long TOLERANCE = TimeUnit.MILLISECONDS.toMillis(200); protected String jwtToken; protected AccessToken accessToken; protected TokenResponse tokenResponse; protected RenewalProvider renewalProvider; protected Caller caller; protected String context; protected boolean initialised = false; public JWTSecret(String jwtToken) { this.jwtToken = jwtToken; } private String getTokenString() { try { boolean expired = false; getAccessToken(); if(Time.currentTimeMillis()>=(accessToken.getExp()-TOLERANCE)) { expired = true; if(tokenResponse!=null) { try { KeycloakClientFactory.newInstance().refreshToken(this.getCaller().getId(), tokenResponse); expired = false; }catch (Exception e) { logger.warn("Unable to refresh the token with RefreshToken. Going to try to renew it if possible.", e); } } } if(expired && renewalProvider!=null) { try { JWTSecret renewed = (JWTSecret) renewalProvider.renew(); this.jwtToken = renewed.jwtToken; this.accessToken = getAccessToken(); }catch (Exception e) { logger.warn("Unable to renew the token with the RenewalProvider. I'll continue using the old token.", e); } } }catch (Exception e) { logger.error("Unexpected error in the procedure to evaluate/refresh the current token. I'll continue using the old token.", e); } return jwtToken; } protected AccessToken getAccessToken() { if(accessToken==null) { String realUmaTokenEncoded = jwtToken.split("\\.")[1]; String realUmaToken = new String(Base64.getDecoder().decode(realUmaTokenEncoded.getBytes())); ObjectMapper mapper = new ObjectMapper(); try { accessToken = mapper.readValue(realUmaToken, AccessToken.class); }catch(Exception e){ logger.error("Error parsing JWT token",e); throw new RuntimeException("Error parsing JWT token", e); } } return accessToken; } private synchronized void init() { if (!initialised) try { ObjectMapper objectMapper = new ObjectMapper(); String accessTokenString = objectMapper.writeValueAsString(getAccessToken()); GCubeJWTObject obj = objectMapper.readValue(accessTokenString, GCubeJWTObject.class); Caller caller = new Caller(obj.getUsername(), obj.getRoles(), obj.getEmail(), obj.getFirstName(), obj.getLastName(), obj.isExternalService()); caller.setClientName(obj.getClientName()); caller.setContactOrganisation(obj.getContactOrganisation()); caller.setClientName(obj.getClientName()); context = obj.getContext(); initialised = true; } catch (Exception e) { throw new RuntimeException(); } } @Override public Caller getCaller() { init(); return this.caller; } @Override public String getContext() { init(); return context; } @Override public Map getHTTPAuthorizationHeaders() { Map authorizationHeaders = new HashMap<>(); authorizationHeaders.put("Authorization", "Bearer " + getTokenString()); return authorizationHeaders; } public void setRenewalProvider(RenewalProvider renewalProvider) { this.renewalProvider = renewalProvider; } public void setTokenResponse(TokenResponse tokenResponse) { this.tokenResponse = tokenResponse; } protected boolean isExpired(AccessToken accessToken) { return Time.currentTimeMillis()>accessToken.getExp(); } @Override public boolean isExpired() { return isExpired(getAccessToken()); } @Override public boolean isRefreshable() { if(tokenResponse!=null) { try { RefreshToken refreshToken = ModelUtils.getRefreshTokenFrom(tokenResponse); return isExpired(refreshToken); } catch (Exception e) { return false; } } return false; } }