common-security/src/main/java/org/gcube/common/security/secrets/JWTSecret.java

165 lines
4.7 KiB
Java

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<String, String> getHTTPAuthorizationHeaders() {
Map<String, String> 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;
}
}