227 lines
6.7 KiB
Java
227 lines
6.7 KiB
Java
package org.gcube.common.authorization.utils.secret;
|
|
|
|
import java.net.URLDecoder;
|
|
import java.net.URLEncoder;
|
|
import java.nio.charset.StandardCharsets;
|
|
import java.util.ArrayList;
|
|
import java.util.Base64;
|
|
import java.util.HashMap;
|
|
import java.util.HashSet;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.TimeUnit;
|
|
|
|
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
|
|
import org.gcube.common.authorization.library.provider.AccessTokenProvider;
|
|
import org.gcube.common.authorization.library.provider.ClientInfo;
|
|
import org.gcube.common.authorization.library.provider.UserInfo;
|
|
import org.gcube.common.authorization.library.utils.Caller;
|
|
import org.gcube.common.authorization.utils.clientid.RenewalProvider;
|
|
import org.gcube.common.authorization.utils.user.KeycloakUser;
|
|
import org.gcube.common.authorization.utils.user.User;
|
|
import org.gcube.common.keycloak.KeycloakClientFactory;
|
|
import org.gcube.common.keycloak.model.AccessToken;
|
|
import org.gcube.common.keycloak.model.AccessToken.Access;
|
|
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.scope.impl.ScopeBean;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
/**
|
|
* @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 AccessToken accessToken;
|
|
protected TokenResponse tokenResponse;
|
|
protected RenewalProvider renewalProvider;
|
|
protected Set<String> roles;
|
|
protected ClientInfo clientInfo;
|
|
protected Caller caller;
|
|
protected String context;
|
|
|
|
public JWTSecret(String token) {
|
|
super(10, token);
|
|
}
|
|
|
|
private String getTokenString() {
|
|
try {
|
|
boolean expired = false;
|
|
getAccessToken();
|
|
|
|
if(Time.currentTimeMillis()>=(accessToken.getExp()-TOLERANCE)) {
|
|
expired = true;
|
|
if(tokenResponse!=null) {
|
|
try {
|
|
KeycloakClientFactory.newInstance().refreshToken(getUsername(), 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.token = renewed.token;
|
|
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 token;
|
|
}
|
|
|
|
@Override
|
|
public void setToken() throws Exception {
|
|
AccessTokenProvider.instance.set(getTokenString());
|
|
}
|
|
|
|
@Override
|
|
public void resetToken() throws Exception {
|
|
AccessTokenProvider.instance.reset();
|
|
}
|
|
|
|
protected AccessToken getAccessToken() {
|
|
if(accessToken==null) {
|
|
String realUmaTokenEncoded = token.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;
|
|
}
|
|
|
|
protected Set<String> getRoles() throws Exception{
|
|
if(roles == null) {
|
|
Map<String,Access> accesses = getAccessToken().getResourceAccess();
|
|
String context = getContext();
|
|
Access access = accesses.get(URLEncoder.encode(context, StandardCharsets.UTF_8.toString()));
|
|
if(access != null) {
|
|
roles = access.getRoles();
|
|
}else {
|
|
roles = new HashSet<>();
|
|
}
|
|
}
|
|
return roles;
|
|
}
|
|
|
|
@Override
|
|
public ClientInfo getClientInfo() throws Exception {
|
|
if(clientInfo==null) {
|
|
getAccessToken();
|
|
List<String> roles = new ArrayList<>(getRoles());
|
|
clientInfo = new UserInfo(accessToken.getPreferredUsername(), roles, accessToken.getEmail(), accessToken.getGivenName(), accessToken.getFamilyName());
|
|
}
|
|
return clientInfo;
|
|
}
|
|
|
|
@Override
|
|
public Caller getCaller() throws Exception {
|
|
if(caller==null) {
|
|
caller = new Caller(getClientInfo(), "token");
|
|
}
|
|
return caller;
|
|
}
|
|
|
|
@Override
|
|
public String getContext() throws Exception {
|
|
if(context==null) {
|
|
String[] audience = getAccessToken().getAudience();
|
|
for (String aud : audience) {
|
|
if (aud != null && aud.compareTo("") != 0) {
|
|
try {
|
|
String contextToBeValidated = URLDecoder.decode(aud, StandardCharsets.UTF_8.toString());
|
|
ScopeBean scopeBean = new ScopeBean(contextToBeValidated);
|
|
context = scopeBean.toString();
|
|
return context;
|
|
} catch (Exception e) {
|
|
logger.error("Invalid context name for audience {} in access token. Trying next one if any.", aud, e);
|
|
}
|
|
}
|
|
}
|
|
throw new Exception("Invalid context in access token");
|
|
}
|
|
return context;
|
|
}
|
|
|
|
@Override
|
|
public String getUsername() throws Exception {
|
|
return getAccessToken().getPreferredUsername();
|
|
}
|
|
|
|
@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;
|
|
}
|
|
|
|
@Override
|
|
public User getUser() {
|
|
if(user==null) {
|
|
try {
|
|
ObjectMapper objectMapper = new ObjectMapper();
|
|
String accessTokenString = objectMapper.writeValueAsString(getAccessToken());
|
|
user = objectMapper.readValue(accessTokenString, KeycloakUser.class);
|
|
user.setRoles(getRoles());
|
|
} catch (Exception e) {
|
|
throw new RuntimeException();
|
|
}
|
|
}
|
|
return user;
|
|
}
|
|
|
|
} |