Backporting fixes

This commit is contained in:
Luca Frosini 2022-03-24 17:58:33 +01:00
parent 464515262f
commit 7ac7682818
9 changed files with 224 additions and 38 deletions

View File

@ -2,7 +2,12 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
# Changelog for Authorization utils
## [v1.0.1-SNAPSHOT]
- Added support for clientID [#21903]
## [v1.0.0]
- First Release

View File

@ -10,7 +10,7 @@
<groupId>org.gcube.common</groupId>
<artifactId>authorization-utils</artifactId>
<version>1.0.0</version>
<version>1.0.1-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

View File

@ -2,6 +2,9 @@ package org.gcube.common.authorization.utils.clientid;
import org.gcube.common.authorization.utils.secret.Secret;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public interface RenewalProvider {
public Secret renew() throws Exception;

View File

@ -105,6 +105,7 @@ public class GCubeSecret extends Secret {
GCubeUser gCubeUser = new GCubeUser();
gCubeUser.setRoles(new HashSet<>(clientInfo.getRoles()));
gCubeUser.setUsername(clientInfo.getId());
gCubeUser.setApplication(true);
user = gCubeUser;
break;
}

View File

@ -1,12 +1,15 @@
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;
@ -19,6 +22,7 @@ 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;
@ -44,6 +48,10 @@ public class JWTSecret extends Secret {
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);
@ -91,7 +99,7 @@ public class JWTSecret extends Secret {
AccessTokenProvider.instance.reset();
}
protected AccessToken getAccessToken() throws Exception {
protected AccessToken getAccessToken() {
if(accessToken==null) {
String realUmaTokenEncoded = token.split("\\.")[1];
String realUmaToken = new String(Base64.getDecoder().decode(realUmaTokenEncoded.getBytes()));
@ -100,47 +108,68 @@ public class JWTSecret extends Secret {
accessToken = mapper.readValue(realUmaToken, AccessToken.class);
}catch(Exception e){
logger.error("Error parsing JWT token",e);
throw new Exception("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 {
getAccessToken();
List<String> roles = new ArrayList<>(accessToken.getRealmAccess().getRoles());
ClientInfo clientInfo = new UserInfo(accessToken.getPreferredUsername(), roles, accessToken.getEmail(), accessToken.getGivenName(), accessToken.getFamilyName());
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 {
Caller caller = new Caller(getClientInfo(), "token");
if(caller==null) {
caller = new Caller(getClientInfo(), "token");
}
return caller;
}
@Override
public String getContext() throws Exception {
String context = null;
String[] audience = getAccessToken().getAudience();
for (String aud : audience) {
if (aud != null && aud.compareTo("") != 0) {
try {
context = URLDecoder.decode(context, StandardCharsets.UTF_8.toString());
ScopeBean scopeBean = new ScopeBean(context);
return scopeBean.toString();
} catch (Exception e) {
logger.error("Invalid context name for audience {} in access token. Trying next one if any.", aud, e);
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");
}
throw new Exception("Invalid context in access token");
return context;
}
@Override
public String getUsername() throws Exception {
return accessToken.getPreferredUsername();
return getAccessToken().getPreferredUsername();
}
@Override
@ -164,7 +193,7 @@ public class JWTSecret extends Secret {
@Override
public boolean isExpired() {
return isExpired(accessToken);
return isExpired(getAccessToken());
}
@Override
@ -185,8 +214,9 @@ public class JWTSecret extends Secret {
if(user==null) {
try {
ObjectMapper objectMapper = new ObjectMapper();
String accessTokenString = objectMapper.writeValueAsString(accessToken);
String accessTokenString = objectMapper.writeValueAsString(getAccessToken());
user = objectMapper.readValue(accessTokenString, KeycloakUser.class);
user.setRoles(getRoles());
} catch (Exception e) {
throw new RuntimeException();
}

View File

@ -2,6 +2,9 @@ package org.gcube.common.authorization.utils.secret;
import java.util.regex.Pattern;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class SecretUtility {
public static final String UUID_REGEX = "^([a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}-[a-fA-F0-9]{8,9}){1}$";

View File

@ -2,6 +2,7 @@ package org.gcube.common.authorization.utils.user;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@ -21,18 +22,6 @@ public class GCubeUser implements User {
@JsonIgnore
protected Map<String, Object> additionalProperties;
public GCubeUser() {
this.additionalProperties = new HashMap<>();
// This info are not always present. Setting an empty string to avoid null
this.givenName = "";
this.familyName = "";
this.eMail = "";
this.jobTitle = "";
this.picture = "";
this.middleName = "";
}
@JsonProperty("id")
protected String username;
@JsonProperty("roles")
@ -52,6 +41,23 @@ public class GCubeUser implements User {
@JsonProperty("middle_name")
protected String middleName;
@JsonIgnore
protected boolean application;
public GCubeUser() {
this.additionalProperties = new HashMap<>();
// This info are not always present. Setting an empty string to avoid null
this.givenName = "";
this.familyName = "";
this.eMail = "";
this.jobTitle = "";
this.picture = "";
this.middleName = "";
this.application = false;
}
@Override
public String getUsername() {
return username;
@ -60,14 +66,24 @@ public class GCubeUser implements User {
public void setUsername(String username) {
this.username = username;
}
@Override
public boolean isApplication() {
return application;
}
public void setApplication(boolean application) {
this.application = application;
}
@Override
public Collection<String> getRoles() {
return roles;
}
public void setRoles(Set<String> roles) {
this.roles = roles;
@Override
public void setRoles(Collection<String> roles) {
this.roles = new HashSet<>(roles);
}
@Override
public String getGivenName() {
@ -115,4 +131,49 @@ public class GCubeUser implements User {
this.additionalProperties.put(key, value);
}
@Override
public String getFullName() {
return getFullName(false);
}
@Override
public String getFullName(boolean nameSurname) {
if(isApplication()) {
return getUsername();
}
StringBuffer stringBuffer = new StringBuffer();
boolean found = false;
String surname = getFamilyName();
String name = getGivenName();
if(nameSurname) {
if(name!=null && name.trim().length()>0) {
stringBuffer.append(name.trim());
found = true;
}
if(surname!=null && surname.trim().length()>0) {
if(found) {
stringBuffer.append(" ");
}
stringBuffer.append(surname.trim());
found = true;
}
}else {
if(surname!=null && surname.trim().length()>0) {
stringBuffer.append(surname.trim());
found = true;
}
if(name!=null && name.trim().length()>0) {
if(found) {
stringBuffer.append(" ");
}
stringBuffer.append(name.trim());
found = true;
}
}
return stringBuffer.toString();
}
}

View File

@ -15,21 +15,86 @@ public class KeycloakUser extends AccessToken implements User {
*/
private static final long serialVersionUID = -7083648026885406300L;
public static final String CLIENT_ID_PROPERTY = "clientId";
protected Collection<String> roles;
protected Boolean application;
@Override
@JsonIgnore
public String getUsername() {
return getId();
return getPreferredUsername();
}
@Override
public boolean isApplication() {
if(application==null) {
application = getOtherClaims().get(CLIENT_ID_PROPERTY)!=null;
}
return application;
}
@Override
@JsonIgnore
public Collection<String> getRoles() {
return getRealmAccess().getRoles();
return roles;
}
@Override
@JsonIgnore
public void setRoles(Collection<String> roles) {
this.roles = roles;
}
@Override
public String getAbout() {
return "";
}
@Override
public String getFullName() {
return getFullName(false);
}
@Override
public String getFullName(boolean nameSurname) {
if(isApplication()) {
String clientID = (String) getOtherClaims().getOrDefault("clientId", getUsername());
return clientID;
}
StringBuffer stringBuffer = new StringBuffer();
boolean found = false;
String surname = getFamilyName();
String name = getGivenName();
if(nameSurname) {
if(name!=null && name.trim().length()>0) {
stringBuffer.append(name.trim());
found = true;
}
if(surname!=null && surname.trim().length()>0) {
if(found) {
stringBuffer.append(" ");
}
stringBuffer.append(surname.trim());
found = true;
}
}else {
if(surname!=null && surname.trim().length()>0) {
stringBuffer.append(surname.trim());
found = true;
}
if(name!=null && name.trim().length()>0) {
if(found) {
stringBuffer.append(" ");
}
stringBuffer.append(name.trim());
found = true;
}
}
return stringBuffer.toString();
}
}

View File

@ -9,12 +9,30 @@ public interface User {
public String getUsername();
public boolean isApplication();
public Collection<String> getRoles();
public void setRoles(Collection<String> roles);
public String getGivenName();
public String getFamilyName();
/**
* @return the full name in the form 'Surname Name' for a person
* or the application identifier for an application;
*/
public String getFullName();
/**
* @param nameSurname when true the fullname will be formatted as 'Name Surname',
* when false the fullname will be formatted as 'Surname Name',
* @return the full name according to nameSurname boolean for a person
* or the application identifier for an application;
*/
public String getFullName(boolean nameSurname);
public String getEmail();
public String getAbout();