Compare commits

...

45 Commits

Author SHA1 Message Date
Luca Frosini 646101d4ef Deprecated class which will be no longer available in Smartgears 4 based
components
2024-05-06 15:19:50 +02:00
Luca Frosini 6859d535f3 Added junit test 2024-05-02 15:55:10 +02:00
Luca Frosini f18dd864da Added support for 'client_id' and backward compatibility with 'clientId'
claim #25802
2024-05-02 15:47:10 +02:00
luca.frosini 0f1a5ad246 Removed whitespace 2023-07-21 14:18:18 +02:00
luca.frosini ac544d083e Removed -SNAPSHOT for release 2023-07-21 14:15:33 +02:00
luca.frosini c0b7cf7165 Fixing library 2023-07-11 10:21:12 +02:00
luca.frosini c7af44e220 Switched to the new version of keycloak-client refs #25295 2023-07-10 15:10:15 +02:00
Luca Frosini 5a29295ca9 Ignored MacOs File 2023-06-21 11:27:57 +02:00
luca.frosini 0f17123724 Ignored MacOs File 2023-06-21 11:25:47 +02:00
Luca Frosini ae07d71dd1 Removed -SNAPSHOT to release the component 2023-03-03 11:55:29 +01:00
Luca Frosini f1ef74cae4 enhanced gcube-bom 2023-03-03 11:54:42 +01:00
Luca Frosini f06166a6ff Added remove() method in SecretManagerProvider 2023-01-13 15:12:27 +01:00
Luca Frosini 3e91121f09 Downgraded gcube-bom 2022-05-04 15:04:37 +02:00
Luca Frosini e624882f10 Removed -SNAPSHOT to release the component 2022-04-28 17:20:38 +02:00
Luca Frosini d54befcbfa Removed -SNAPSHOT to release the component 2022-04-28 17:16:30 +02:00
Luca Frosini 2ab160275c fixed exception 2022-03-30 15:04:09 +02:00
Luca Frosini dab0deea20 Removed OIDC Client credential 2022-03-30 15:03:50 +02:00
Luca Frosini 67a851a5d1 Added OIDC Client credential [#23089] 2022-03-30 14:29:40 +02:00
Luca Frosini bd7ef7ff1c Fixed constant 2022-03-29 18:34:33 +02:00
Luca Frosini 4617573a26 Setting ExternalServiceInfo when application 2022-03-29 14:59:36 +02:00
Luca Frosini 0286521cfe Improved code 2022-03-29 14:43:05 +02:00
Luca Frosini 356da003c7 Added getSecret for a specific context 2022-03-29 12:43:25 +02:00
Luca Frosini 3fbcaf37ad Avoiding null pointer exceptions 2022-03-24 14:30:16 +01:00
Luca Frosini d2d8686259 Set application when not user 2022-03-24 14:27:31 +01:00
Luca Frosini 781729ff6d Fixed Test 2022-03-24 14:27:09 +01:00
Luca Frosini 66fd1afc7c Implemented isApplication() and getFullName() APIs 2022-03-24 14:26:18 +01:00
Luca Frosini 6e271a5772 Added isApplication and getFullName APIs 2022-03-24 14:25:21 +01:00
Luca Frosini d8b9fecee2 Fixed getUsername 2022-03-24 12:42:21 +01:00
Luca Frosini 1c10f36883 Fixing getUsername 2022-03-22 14:55:52 +01:00
Luca Frosini 74182ab00d Fixed method 2022-03-08 11:25:21 +01:00
Luca Frosini cf1e6f0602 Added missing API and test 2022-03-08 10:16:57 +01:00
Luca Frosini e714b058df Fixed code 2022-03-01 11:36:00 +01:00
Luca Frosini a495c5871e added authorship 2022-02-28 18:04:49 +01:00
Luca Frosini 5fda71ee60 fixing code 2022-02-28 17:07:46 +01:00
Luca Frosini 15748c727a Added logging information 2022-02-28 13:26:08 +01:00
Luca Frosini b9e78300a6 Changing thread local paradigm 2022-02-25 18:08:51 +01:00
Luca Frosini e9493eec19 enhanced gcube-bom version 2022-02-25 16:24:14 +01:00
Luca Frosini 7c9cca32c4 Fixed method 2022-02-25 16:10:10 +01:00
Luca Frosini 8e447014f8 Improved changelog 2022-02-25 15:06:30 +01:00
Luca Frosini 8711dc78b7 fixed code 2022-02-25 14:34:37 +01:00
Luca Frosini 261e8733b2 Refactoring library to be properly used as provider in Smartgears 2022-02-25 12:58:24 +01:00
Luca Frosini a66c1da525 Removed code not needed if the library is integrated in Smartgears 2022-02-25 11:37:19 +01:00
Luca Frosini 6731b8f6a6 Added setRoles 2022-01-31 15:48:03 +01:00
Luca Frosini 778a7cba0b Fixed provider class 2022-01-31 15:47:49 +01:00
Luca Frosini 6c9dd41851 Fixed getRoles refs #22754 2022-01-31 15:47:02 +01:00
21 changed files with 662 additions and 161 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
/.project /.project
/.classpath /.classpath
/.settings /.settings
/.DS_Store

View File

@ -1,6 +1,25 @@
This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
# Changelog for Authorization utils # Changelog for Authorization Utils
## [v2.3.0-SNAPSHOT]
- Added support for 'client_id' and backward compatibility with 'clientId' claim #25802
- Deprecated class which will be no longer available in Smartgears 4 based components
## [v2.2.0]
- Switched to the new version of keycloak-client [#25295]
## [v2.1.0]
- Added remove() method in SecretManagerProvider
- Enhanced gcube-bom version
## [v2.0.0]
- Refactored code to be integrated in Smartgears [#22871]
- Fixed getRoles for JWTSecret [#22754]
## [v1.0.0] ## [v1.0.0]

View File

@ -13,7 +13,7 @@ N/A
## Change log ## Change log
See [Releases](https://code-repo.d4science.org/gCubeSystem/gcat/releases). See [CHANGELOG.md](CHANGELOG.md).
## Authors ## Authors
@ -41,6 +41,7 @@ This project is licensed under the EUPL V.1.1 License - see the [LICENSE.md](LIC
## About the gCube Framework ## About the gCube Framework
This software is part of the [gCubeFramework](https://www.gcube-system.org/ "gCubeFramework"): an This software is part of the [gCubeFramework](https://www.gcube-system.org/ "gCubeFramework"): an
open-source software toolkit used for building and operating Hybrid Data open-source software toolkit used for building and operating Hybrid Data
Infrastructures enabling the dynamic deployment of Virtual Research Environments Infrastructures enabling the dynamic deployment of Virtual Research Environments

31
pom.xml
View File

@ -10,7 +10,7 @@
<groupId>org.gcube.common</groupId> <groupId>org.gcube.common</groupId>
<artifactId>authorization-utils</artifactId> <artifactId>authorization-utils</artifactId>
<version>1.0.0</version> <version>2.3.0-SNAPSHOT</version>
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -27,7 +27,7 @@
<dependency> <dependency>
<groupId>org.gcube.distribution</groupId> <groupId>org.gcube.distribution</groupId>
<artifactId>gcube-bom</artifactId> <artifactId>gcube-bom</artifactId>
<version>2.0.1</version> <version>2.4.0</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@ -62,7 +62,32 @@
<dependency> <dependency>
<groupId>org.gcube.common</groupId> <groupId>org.gcube.common</groupId>
<artifactId>keycloak-client</artifactId> <artifactId>keycloak-client</artifactId>
<version>[1.0.0,2.0.0-SNAPSHOT)</version> </dependency>
<dependency>
<groupId>org.gcube.resources</groupId>
<artifactId>common-gcore-resources</artifactId>
</dependency>
<dependency>
<groupId>org.gcube.resources.discovery</groupId>
<artifactId>ic-client</artifactId>
</dependency>
<dependency>
<groupId>org.gcube.resources.discovery</groupId>
<artifactId>discovery-client</artifactId>
</dependency>
<!-- Test Dependencies -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -8,18 +8,18 @@ import org.gcube.common.keycloak.model.TokenResponse;
/** /**
* @author Luca Frosini (ISTI - CNR) * @author Luca Frosini (ISTI - CNR)
*/ */
public class ClienIDManager implements RenewalProvider { public class ClientIDManager implements RenewalProvider {
protected final String clientID; protected final String clientID;
protected final String clientSecret; protected final String clientSecret;
public ClienIDManager(String clientID, String clientSecret) { public ClientIDManager(String clientID, String clientSecret) {
this.clientID = clientID; this.clientID = clientID;
this.clientSecret = clientSecret; this.clientSecret = clientSecret;
} }
public Secret getSecret() throws Exception { public Secret getSecret(String context) throws Exception {
TokenResponse tokenResponse = KeycloakClientFactory.newInstance().queryUMAToken(clientID, clientSecret, null); TokenResponse tokenResponse = KeycloakClientFactory.newInstance().queryUMAToken(context, clientID, clientSecret, context, null);
JWTSecret jwtSecret = new JWTSecret(tokenResponse.getAccessToken()); JWTSecret jwtSecret = new JWTSecret(tokenResponse.getAccessToken());
jwtSecret.setRenewalProvider(this); jwtSecret.setRenewalProvider(this);
@ -28,9 +28,10 @@ public class ClienIDManager implements RenewalProvider {
return jwtSecret; return jwtSecret;
} }
@Override @Override
public Secret renew() throws Exception { public Secret renew(String context) throws Exception {
return getSecret(); return getSecret(context);
} }
} }

View File

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

View File

@ -34,7 +34,7 @@ public class SecretHolder {
} }
} }
public void addAuthorizationSecrets(Collection<Secret> secrets) { public void addSecrets(Collection<Secret> secrets) {
for(Secret secret : secrets){ for(Secret secret : secrets){
addSecret(secret); addSecret(secret);
} }
@ -85,13 +85,22 @@ public class SecretHolder {
} }
public void reset() { public void reset() {
boolean first = true;
for(Secret secret : secrets) { for(Secret secret : secrets) {
try { try {
secret.reset(); if(first) {
secret.reset();
first = false;
}else {
secret.resetToken();
}
}catch (Exception e) { }catch (Exception e) {
// trying the next one // trying the next one
} }
} }
if(first) {
ScopeProvider.instance.reset();
}
} }
} }

View File

@ -1,102 +1,116 @@
package org.gcube.common.authorization.utils.manager; package org.gcube.common.authorization.utils.manager;
import java.util.ArrayList; import java.util.Collection;
import java.util.List;
import java.util.SortedSet; import java.util.SortedSet;
import java.util.TreeSet;
import org.gcube.common.authorization.utils.provider.GCubeSecretProvider;
import org.gcube.common.authorization.utils.provider.SecretProvider; import org.gcube.common.authorization.utils.provider.SecretProvider;
import org.gcube.common.authorization.utils.secret.JWTSecret;
import org.gcube.common.authorization.utils.secret.Secret; import org.gcube.common.authorization.utils.secret.Secret;
import org.gcube.common.authorization.utils.secret.SecretUtility;
import org.gcube.common.authorization.utils.user.User; import org.gcube.common.authorization.utils.user.User;
/** /**
* @author Luca Frosini (ISTI - CNR) * @author Luca Frosini (ISTI - CNR)
*/ */
public class SecretManager { public class SecretManager {
public static final InheritableThreadLocal<SecretManager> instance = new InheritableThreadLocal<SecretManager>() {
@Override
protected SecretManager initialValue() {
return new SecretManager();
}
};
private List<SecretProvider> secretProviders;
private SecretHolder initialSecretHolder; private SecretHolder initialSecretHolder;
private SecretHolder currentSecretHolder; private SecretHolder currentSecretHolder;
private SecretManager(){ public SecretManager() {
initialSecretHolder = new SecretHolder(); initialSecretHolder = new SecretHolder();
currentSecretHolder = initialSecretHolder; currentSecretHolder = initialSecretHolder;
initSecretProviders();
} }
protected void initSecretProviders() { public synchronized void addSecretViaProvider(SecretProvider secretProvider) {
secretProviders = new ArrayList<>(); if (currentSecretHolder != initialSecretHolder) {
throw new RuntimeException("You can't add a Secret in a session. You must terminate the session first.");
@SuppressWarnings("unchecked")
Class<SecretProvider>[] classes = new Class[]{
JWTSecret.class, GCubeSecretProvider.class
};
for(Class<SecretProvider> clz : classes) {
try {
SecretProvider authorizationSecretProvider = clz.newInstance();
addSecretProvider(authorizationSecretProvider);
} catch (Exception e) {
}
} }
}
public List<SecretProvider> getSecretProviders(){
return secretProviders;
}
public void addSecretProvider(SecretProvider secretProvider) {
secretProviders.add(secretProvider);
Secret secret = secretProvider.getSecret(); Secret secret = secretProvider.getSecret();
initialSecretHolder.addSecret(secret); currentSecretHolder.addSecret(secret);
} }
public synchronized void startSession(Secret secret) throws Exception { public synchronized void addSecret(Secret secret) {
if(currentSecretHolder!=initialSecretHolder) { if (currentSecretHolder != initialSecretHolder) {
throw new Exception("You are already in a session. You must terminate the session first."); throw new RuntimeException("You can't add a Secret in a session. You must terminate the session first.");
} }
currentSecretHolder.addSecret(secret);
}
public synchronized void startSession(Secret secret) throws Exception {
if (currentSecretHolder != initialSecretHolder) {
throw new RuntimeException("You are already in a session. You must terminate the session first.");
}
initialSecretHolder.reset();
currentSecretHolder = new SecretHolder(secret); currentSecretHolder = new SecretHolder(secret);
currentSecretHolder.set(); currentSecretHolder.set();
} }
public synchronized void startSession(SortedSet<Secret> secrets) throws Exception { public synchronized void startSession(Collection<Secret> secrets) throws Exception {
if (currentSecretHolder != initialSecretHolder) {
throw new RuntimeException("You are already in a session. You must terminate the session first.");
}
initialSecretHolder.reset();
currentSecretHolder = new SecretHolder(secrets); currentSecretHolder = new SecretHolder(secrets);
currentSecretHolder.set(); currentSecretHolder.set();
} }
public synchronized void endSession() throws Exception { public synchronized void startSession(SecretHolder secretHolder) throws Exception {
if(currentSecretHolder!=initialSecretHolder) { if (currentSecretHolder != initialSecretHolder) {
initialSecretHolder.set(); throw new RuntimeException("You are already in a session. You must terminate the session first.");
}
initialSecretHolder.reset();
currentSecretHolder = secretHolder;
currentSecretHolder.set();
}
public synchronized void endSession() {
if (currentSecretHolder != initialSecretHolder) {
currentSecretHolder.reset();
try {
initialSecretHolder.set();
}catch (Exception e) {
throw new RuntimeException(e);
}
currentSecretHolder = initialSecretHolder; currentSecretHolder = initialSecretHolder;
} }
} }
public void reset() { public synchronized void set() throws Exception {
initialSecretHolder.reset(); if (currentSecretHolder != initialSecretHolder) {
if(initialSecretHolder!=currentSecretHolder) { throw new Exception("You are in a session. You must terminate the session first.");
currentSecretHolder.reset();
} }
instance.remove(); currentSecretHolder.set();
} }
public String getContext() { public synchronized void reset() {
currentSecretHolder.reset();
if (initialSecretHolder != currentSecretHolder) {
initialSecretHolder.reset();
}
}
public synchronized String getContext() {
return currentSecretHolder.getContext(); return currentSecretHolder.getContext();
} }
public User getUser() { public synchronized User getUser() {
return currentSecretHolder.getUser(); return currentSecretHolder.getUser();
} }
/**
* @return a copy of the current secret holder
* to avoid modification to the original
*/
public synchronized SecretHolder getCurrentSecretHolder() {
SecretHolder secretHolder = new SecretHolder();
SortedSet<Secret> secrets = new TreeSet<>();
SortedSet<Secret> originalSecrets = currentSecretHolder.getSecrets();
for(Secret s : originalSecrets) {
Secret secret = SecretUtility.getSecretByTokenString(s.getToken());
secrets.add(secret);
}
secretHolder.addSecrets(secrets);
return secretHolder;
}
} }

View File

@ -0,0 +1,42 @@
package org.gcube.common.authorization.utils.manager;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class SecretManagerProvider {
public static SecretManagerProvider instance = new SecretManagerProvider();
// Thread local variable containing each thread's ID
private static final InheritableThreadLocal<SecretManager> thread = new InheritableThreadLocal<SecretManager>() {
@Override
protected SecretManager initialValue() {
return null;
}
};
private SecretManagerProvider(){}
public SecretManager get(){
SecretManager secretManager = thread.get();
return secretManager;
}
public void set(SecretManager secretManager){
thread.set(secretManager);
}
public void reset(){
SecretManager secretManager = thread.get();
if(secretManager!=null) {
secretManager.reset();
}
thread.remove();
}
public void remove(){
thread.remove();
}
}

View File

@ -1,21 +0,0 @@
package org.gcube.common.authorization.utils.provider;
import org.gcube.common.authorization.library.provider.SecurityTokenProvider;
import org.gcube.common.authorization.utils.secret.Secret;
import org.gcube.common.authorization.utils.secret.GCubeSecret;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class GCubeSecretProvider implements SecretProvider {
@Override
public Secret getSecret() {
String token = SecurityTokenProvider.instance.get();
if(token!=null) {
return new GCubeSecret(token);
}
return null;
}
}

View File

@ -1,21 +0,0 @@
package org.gcube.common.authorization.utils.provider;
import org.gcube.common.authorization.library.provider.AccessTokenProvider;
import org.gcube.common.authorization.utils.secret.Secret;
import org.gcube.common.authorization.utils.secret.JWTSecret;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class JWTSecretProvider implements SecretProvider {
@Override
public Secret getSecret() {
String token = AccessTokenProvider.instance.get();
if(token!=null) {
return new JWTSecret(token);
}
return null;
}
}

View File

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

View File

@ -1,17 +1,20 @@
package org.gcube.common.authorization.utils.secret; package org.gcube.common.authorization.utils.secret;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.common.authorization.library.provider.AccessTokenProvider; import org.gcube.common.authorization.library.provider.AccessTokenProvider;
import org.gcube.common.authorization.library.provider.ClientInfo; import org.gcube.common.authorization.library.provider.ClientInfo;
import org.gcube.common.authorization.library.provider.ExternalServiceInfo;
import org.gcube.common.authorization.library.provider.UserInfo; import org.gcube.common.authorization.library.provider.UserInfo;
import org.gcube.common.authorization.library.utils.Caller; import org.gcube.common.authorization.library.utils.Caller;
import org.gcube.common.authorization.utils.clientid.RenewalProvider; import org.gcube.common.authorization.utils.clientid.RenewalProvider;
@ -19,6 +22,7 @@ import org.gcube.common.authorization.utils.user.KeycloakUser;
import org.gcube.common.authorization.utils.user.User; import org.gcube.common.authorization.utils.user.User;
import org.gcube.common.keycloak.KeycloakClientFactory; import org.gcube.common.keycloak.KeycloakClientFactory;
import org.gcube.common.keycloak.model.AccessToken; 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.ModelUtils;
import org.gcube.common.keycloak.model.RefreshToken; import org.gcube.common.keycloak.model.RefreshToken;
import org.gcube.common.keycloak.model.TokenResponse; import org.gcube.common.keycloak.model.TokenResponse;
@ -44,6 +48,10 @@ public class JWTSecret extends Secret {
protected AccessToken accessToken; protected AccessToken accessToken;
protected TokenResponse tokenResponse; protected TokenResponse tokenResponse;
protected RenewalProvider renewalProvider; protected RenewalProvider renewalProvider;
protected Set<String> roles;
protected ClientInfo clientInfo;
protected Caller caller;
protected String context;
public JWTSecret(String token) { public JWTSecret(String token) {
super(10, token); super(10, token);
@ -68,7 +76,7 @@ public class JWTSecret extends Secret {
if(expired && renewalProvider!=null) { if(expired && renewalProvider!=null) {
try { try {
JWTSecret renewed = (JWTSecret) renewalProvider.renew(); JWTSecret renewed = (JWTSecret) renewalProvider.renew(getContext());
this.token = renewed.token; this.token = renewed.token;
this.accessToken = getAccessToken(); this.accessToken = getAccessToken();
}catch (Exception e) { }catch (Exception e) {
@ -91,7 +99,7 @@ public class JWTSecret extends Secret {
AccessTokenProvider.instance.reset(); AccessTokenProvider.instance.reset();
} }
protected AccessToken getAccessToken() throws Exception { protected AccessToken getAccessToken() {
if(accessToken==null) { if(accessToken==null) {
String realUmaTokenEncoded = token.split("\\.")[1]; String realUmaTokenEncoded = token.split("\\.")[1];
String realUmaToken = new String(Base64.getDecoder().decode(realUmaTokenEncoded.getBytes())); String realUmaToken = new String(Base64.getDecoder().decode(realUmaTokenEncoded.getBytes()));
@ -100,47 +108,71 @@ public class JWTSecret extends Secret {
accessToken = mapper.readValue(realUmaToken, AccessToken.class); accessToken = mapper.readValue(realUmaToken, AccessToken.class);
}catch(Exception e){ }catch(Exception e){
logger.error("Error parsing JWT token",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; 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 @Override
public ClientInfo getClientInfo() throws Exception { public ClientInfo getClientInfo() throws Exception {
getAccessToken(); if(clientInfo==null) {
List<String> roles = new ArrayList<>(accessToken.getRealmAccess().getRoles()); User user = getUser();
ClientInfo clientInfo = new UserInfo(accessToken.getPreferredUsername(), roles, accessToken.getEmail(), accessToken.getGivenName(), accessToken.getFamilyName()); if(user.isApplication()) {
clientInfo = new ExternalServiceInfo(user.getUsername(), "unknown");
}else {
clientInfo = new UserInfo(user.getUsername(), new ArrayList<>(user.getRoles()), user.getEmail(), user.getGivenName(), user.getFamilyName());
}
}
return clientInfo; return clientInfo;
} }
@Override @Override
public Caller getCaller() throws Exception { public Caller getCaller() throws Exception {
Caller caller = new Caller(getClientInfo(), "token"); if(caller==null) {
caller = new Caller(getClientInfo(), "token");
}
return caller; return caller;
} }
@Override @Override
public String getContext() throws Exception { public String getContext() throws Exception {
String context = null; if(context==null) {
String[] audience = getAccessToken().getAudience(); String[] audience = getAccessToken().getAudience();
for (String aud : audience) { for (String aud : audience) {
if (aud != null && aud.compareTo("") != 0) { if (aud != null && aud.compareTo("") != 0) {
try { try {
context = URLDecoder.decode(context, StandardCharsets.UTF_8.toString()); String contextToBeValidated = URLDecoder.decode(aud, StandardCharsets.UTF_8.toString());
ScopeBean scopeBean = new ScopeBean(context); ScopeBean scopeBean = new ScopeBean(contextToBeValidated);
return scopeBean.toString(); context = scopeBean.toString();
} catch (Exception e) { return context;
logger.error("Invalid context name for audience {} in access token. Trying next one if any.", aud, e); } 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 @Override
public String getUsername() throws Exception { public String getUsername() throws Exception {
return accessToken.getPreferredUsername(); return getAccessToken().getPreferredUsername();
} }
@Override @Override
@ -164,7 +196,7 @@ public class JWTSecret extends Secret {
@Override @Override
public boolean isExpired() { public boolean isExpired() {
return isExpired(accessToken); return isExpired(getAccessToken());
} }
@Override @Override
@ -185,8 +217,9 @@ public class JWTSecret extends Secret {
if(user==null) { if(user==null) {
try { try {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
String accessTokenString = objectMapper.writeValueAsString(accessToken); String accessTokenString = objectMapper.writeValueAsString(getAccessToken());
user = objectMapper.readValue(accessTokenString, KeycloakUser.class); user = objectMapper.readValue(accessTokenString, KeycloakUser.class);
user.setRoles(getRoles());
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException(); throw new RuntimeException();
} }

View File

@ -2,6 +2,12 @@ package org.gcube.common.authorization.utils.secret;
import java.util.regex.Pattern; import java.util.regex.Pattern;
/**
* @author Luca Frosini (ISTI - CNR)
* This call will be no more available in
* component Smartgears 4 based
*/
@Deprecated
public class SecretUtility { 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}$"; 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

@ -19,6 +19,7 @@ import javax.ws.rs.core.Response.Status;
import org.gcube.com.fasterxml.jackson.databind.ObjectMapper; import org.gcube.com.fasterxml.jackson.databind.ObjectMapper;
import org.gcube.common.authorization.utils.manager.SecretManager; import org.gcube.common.authorization.utils.manager.SecretManager;
import org.gcube.common.authorization.utils.manager.SecretManagerProvider;
import org.gcube.common.authorization.utils.secret.Secret; import org.gcube.common.authorization.utils.secret.Secret;
import org.gcube.common.authorization.utils.user.GCubeUser; import org.gcube.common.authorization.utils.user.GCubeUser;
import org.gcube.common.authorization.utils.user.User; import org.gcube.common.authorization.utils.user.User;
@ -37,7 +38,7 @@ public class SocialService {
private static final String RESOURCE = "jersey-servlet"; private static final String RESOURCE = "jersey-servlet";
private static final String SERVICE_NAME = "SocialNetworking"; private static final String SERVICE_NAME = "SocialNetworking";
private static final String SERVICE_CLASSE = "Portal"; private static final String SERVICE_CLASS = "Portal";
private static Logger logger = LoggerFactory.getLogger(SocialService.class); private static Logger logger = LoggerFactory.getLogger(SocialService.class);
private String serviceBasePath; private String serviceBasePath;
@ -50,7 +51,8 @@ public class SocialService {
} }
public static SocialService getSocialService() throws Exception { public static SocialService getSocialService() throws Exception {
String context = SecretManager.instance.get().getContext(); SecretManager secretManager = SecretManagerProvider.instance.get();
String context = secretManager.getContext();
SocialService socialService = socialServicePerContext.get(context); SocialService socialService = socialServicePerContext.get(context);
if(socialService == null) { if(socialService == null) {
socialService = new SocialService(); socialService = new SocialService();
@ -68,9 +70,10 @@ public class SocialService {
} }
protected void getServiceBasePathViaGCoreEndpoint() throws Exception { protected void getServiceBasePathViaGCoreEndpoint() throws Exception {
SecretManager secretManager = SecretManagerProvider.instance.get();
try { try {
SimpleQuery query = queryFor(GCoreEndpoint.class); SimpleQuery query = queryFor(GCoreEndpoint.class);
query.addCondition(String.format("$resource/Profile/ServiceClass/text() eq '%s'", SERVICE_CLASSE)); query.addCondition(String.format("$resource/Profile/ServiceClass/text() eq '%s'", SERVICE_CLASS));
query.addCondition("$resource/Profile/DeploymentData/Status/text() eq 'ready'"); query.addCondition("$resource/Profile/DeploymentData/Status/text() eq 'ready'");
query.addCondition(String.format("$resource/Profile/ServiceName/text() eq '%s'", SERVICE_NAME)); query.addCondition(String.format("$resource/Profile/ServiceName/text() eq '%s'", SERVICE_NAME));
query.setResult( query.setResult(
@ -81,20 +84,20 @@ public class SocialService {
List<String> endpoints = client.submit(query); List<String> endpoints = client.submit(query);
if(endpoints == null || endpoints.isEmpty()) { if(endpoints == null || endpoints.isEmpty()) {
throw new Exception("Cannot retrieve the GCoreEndpoint SERVICE_NAME: " + SERVICE_NAME throw new Exception("Cannot retrieve the GCoreEndpoint SERVICE_NAME: " + SERVICE_NAME
+ ", SERVICE_CLASSE: " + SERVICE_CLASSE + ", in scope: " + SecretManager.instance.get().getContext()); + ", SERVICE_CLASSE: " + SERVICE_CLASS + ", in scope: " + secretManager.getContext());
} }
this.serviceBasePath = endpoints.get(0); this.serviceBasePath = endpoints.get(0);
if(serviceBasePath == null) if(serviceBasePath == null)
throw new Exception("Endpoint:" + RESOURCE + ", is null for SERVICE_NAME: " + SERVICE_NAME throw new Exception("Endpoint:" + RESOURCE + ", is null for SERVICE_NAME: " + SERVICE_NAME
+ ", SERVICE_CLASSE: " + SERVICE_CLASSE + ", in scope: " + SecretManager.instance.get().getContext()); + ", SERVICE_CLASSE: " + SERVICE_CLASS + ", in scope: " + secretManager.getContext());
serviceBasePath = serviceBasePath.endsWith("/") ? serviceBasePath : serviceBasePath + "/"; serviceBasePath = serviceBasePath.endsWith("/") ? serviceBasePath : serviceBasePath + "/";
} catch(Exception e) { } catch(Exception e) {
String error = "An error occurred during GCoreEndpoint discovery, SERVICE_NAME: " + SERVICE_NAME String error = "An error occurred during GCoreEndpoint discovery, SERVICE_NAME: " + SERVICE_NAME
+ ", SERVICE_CLASSE: " + SERVICE_CLASSE + ", in scope: " + SecretManager.instance.get().getContext() + "."; + ", SERVICE_CLASSE: " + SERVICE_CLASS + ", in scope: " + secretManager.getContext() + ".";
logger.error(error, e); logger.error(error, e);
throw new Exception(error); throw new Exception(error);
} }

View File

@ -2,6 +2,7 @@ package org.gcube.common.authorization.utils.user;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -21,18 +22,6 @@ public class GCubeUser implements User {
@JsonIgnore @JsonIgnore
protected Map<String, Object> additionalProperties; 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") @JsonProperty("id")
protected String username; protected String username;
@JsonProperty("roles") @JsonProperty("roles")
@ -52,6 +41,23 @@ public class GCubeUser implements User {
@JsonProperty("middle_name") @JsonProperty("middle_name")
protected String middleName; 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 @Override
public String getUsername() { public String getUsername() {
return username; return username;
@ -60,14 +66,24 @@ public class GCubeUser implements User {
public void setUsername(String username) { public void setUsername(String username) {
this.username = username; this.username = username;
} }
@Override
public boolean isApplication() {
return application;
}
public void setApplication(boolean application) {
this.application = application;
}
@Override @Override
public Collection<String> getRoles() { public Collection<String> getRoles() {
return roles; return roles;
} }
public void setRoles(Set<String> roles) { @Override
this.roles = roles; public void setRoles(Collection<String> roles) {
this.roles = new HashSet<>(roles);
} }
@Override @Override
public String getGivenName() { public String getGivenName() {
@ -115,4 +131,49 @@ public class GCubeUser implements User {
this.additionalProperties.put(key, value); 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,99 @@ public class KeycloakUser extends AccessToken implements User {
*/ */
private static final long serialVersionUID = -7083648026885406300L; private static final long serialVersionUID = -7083648026885406300L;
public static final String CLIENT_ID_PROPERTY_OLD_KEY = "clientId";
public static final String CLIENT_ID_PROPERTY = "client_id";
protected Collection<String> roles;
protected Boolean application;
@Override @Override
@JsonIgnore @JsonIgnore
public String getUsername() { public String getUsername() {
return getId(); return getPreferredUsername();
} }
@JsonIgnore
protected String getClientId() {
Object clientIdObj = getOtherClaims().get(CLIENT_ID_PROPERTY);
if(clientIdObj==null) {
clientIdObj = getOtherClaims().get(CLIENT_ID_PROPERTY_OLD_KEY);
}
return clientIdObj==null ? null : clientIdObj.toString();
}
@Override
public boolean isApplication() {
if(application==null) {
application = getClientId()!=null;
}
return application;
}
@Override @Override
@JsonIgnore @JsonIgnore
public Collection<String> getRoles() { public Collection<String> getRoles() {
return getRealmAccess().getRoles(); return roles;
}
@Override
@JsonIgnore
public void setRoles(Collection<String> roles) {
this.roles = roles;
} }
@Override @Override
public String getAbout() { public String getAbout() {
return ""; return "";
} }
@Override
public String getFullName() {
return getFullName(false);
}
@Override
public String getFullName(boolean nameSurname) {
if(isApplication()) {
String clientID = getClientId();
if(clientID==null) {
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 String getUsername();
public boolean isApplication();
public Collection<String> getRoles(); public Collection<String> getRoles();
public void setRoles(Collection<String> roles);
public String getGivenName(); public String getGivenName();
public String getFamilyName(); 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 getEmail();
public String getAbout(); public String getAbout();

View File

@ -0,0 +1,181 @@
/**
*
*/
package org.gcube.common.authorization.utils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
import org.gcube.common.authorization.utils.manager.SecretManager;
import org.gcube.common.authorization.utils.manager.SecretManagerProvider;
import org.gcube.common.authorization.utils.secret.JWTSecret;
import org.gcube.common.authorization.utils.secret.Secret;
import org.gcube.common.authorization.utils.secret.SecretUtility;
import org.gcube.common.keycloak.KeycloakClientFactory;
import org.gcube.common.keycloak.KeycloakClientHelper;
import org.gcube.common.keycloak.model.TokenResponse;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class ContextTest {
private static final Logger logger = LoggerFactory.getLogger(ContextTest.class);
protected static final String CONFIG_INI_FILENAME = "config.ini";
public static final String DEFAULT_TEST_SCOPE;
public static final String GCUBE;
public static final String DEVNEXT;
public static final String NEXTNEXT;
public static final String DEVSEC;
public static final String DEVVRE;
private static final String ROOT_PRE;
private static final String VO_PREPROD;
protected static final String VRE_GRSF_PRE;
private static final String ROOT_PROD;
protected static final Properties properties;
public static final String TYPE_PROPERTY_KEY = "type";
public static final String USERNAME_PROPERTY_KEY = "username";
public static final String PASSWORD_PROPERTY_KEY = "password";
public static final String CLIENT_ID_PROPERTY_KEY = "clientId";
static {
GCUBE = "/gcube";
DEVNEXT = GCUBE + "/devNext";
NEXTNEXT = DEVNEXT + "/NextNext";
DEVSEC = GCUBE + "/devsec";
DEVVRE = DEVSEC + "/devVRE";
ROOT_PRE = "/pred4s";
VO_PREPROD = ROOT_PRE + "/preprod";
VRE_GRSF_PRE = VO_PREPROD + "/GRSF_Pre";
ROOT_PROD = "/d4science.research-infrastructures.eu";
DEFAULT_TEST_SCOPE = DEVVRE;
// DEFAULT_TEST_SCOPE = VRE_GRSF_PRE;
properties = new Properties();
InputStream input = ContextTest.class.getClassLoader().getResourceAsStream(CONFIG_INI_FILENAME);
try {
// load the properties file
properties.load(input);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private enum Type{
USER, CLIENT_ID
};
public static void set(Secret secret) throws Exception {
SecretManagerProvider.instance.reset();
SecretManager secretManager = new SecretManager();
secretManager.addSecret(secret);
SecretManagerProvider.instance.set(secretManager);
SecretManagerProvider.instance.get().set();
}
public static void setContextByName(String fullContextName) throws Exception {
logger.debug("Going to set credentials for context {}", fullContextName);
Secret secret = getSecretByContextName(fullContextName);
set(secret);
}
private static TokenResponse getJWTAccessToken(String context) throws Exception {
Type type = Type.valueOf(properties.get(TYPE_PROPERTY_KEY).toString());
TokenResponse tr = null;
int index = context.indexOf('/', 1);
String root = context.substring(0, index == -1 ? context.length() : index);
switch (type) {
case CLIENT_ID:
String clientId = properties.getProperty(CLIENT_ID_PROPERTY_KEY);
String clientSecret = properties.getProperty(root);
tr = KeycloakClientFactory.newInstance().queryUMAToken(context, clientId, clientSecret, context, null);
break;
case USER:
default:
String username = properties.getProperty(USERNAME_PROPERTY_KEY);
String password = properties.getProperty(PASSWORD_PROPERTY_KEY);
switch (root) {
case "/gcube":
default:
clientId = "next.d4science.org";
break;
case "/pred4s":
clientId = "pre.d4science.org";
break;
case "/d4science.research-infrastructures.eu":
clientId = "services.d4science.org";
break;
}
clientSecret = null;
tr = KeycloakClientHelper.getTokenForUser(context, username, password);
break;
}
return tr;
}
public static Secret getSecretByContextName(String context) throws Exception {
TokenResponse tr = getJWTAccessToken(context);
Secret secret = new JWTSecret(tr.getAccessToken());
return secret;
}
public static void setContext(String token) throws Exception {
Secret secret = getSecret(token);
set(secret);
}
private static Secret getSecret(String token) throws Exception {
Secret secret = SecretUtility.getSecretByTokenString(token);
return secret;
}
public static String getUser() {
String user = "UNKNOWN";
try {
user = SecretManagerProvider.instance.get().getUser().getUsername();
} catch(Exception e) {
logger.error("Unable to retrieve user. {} will be used", user);
}
return user;
}
@BeforeClass
public static void beforeClass() throws Exception {
setContextByName(DEFAULT_TEST_SCOPE);
}
@AfterClass
public static void afterClass() throws Exception {
SecretManagerProvider.instance.reset();
}
}

View File

@ -0,0 +1,44 @@
package org.gcube.common.authorization.utils.manager;
import java.util.Set;
import org.gcube.common.authorization.utils.ContextTest;
import org.gcube.common.authorization.utils.secret.JWTSecret;
import org.gcube.common.authorization.utils.secret.Secret;
import org.gcube.common.authorization.utils.user.User;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @author Luca Frosini (ISTI - CNR)
*/
public class SecretManagerTest extends ContextTest {
private static final Logger logger = LoggerFactory.getLogger(SecretManagerTest.class);
@Test
public void test() throws Exception {
SecretManager secretManager = SecretManagerProvider.instance.get();
SecretHolder secretHolder = secretManager.getCurrentSecretHolder();
Set<Secret> secrets = secretHolder.getSecrets();
for(Secret s : secrets) {
logger.debug("{}: token={}", s.getClass().getSimpleName(), s.getToken());
User user = s.getUser();
String username = user.getUsername();
String surnameName = user.getFullName();
String nameSurname = user.getFullName(true);
logger.debug("{} - {} - {}", username, surnameName, nameSurname);
}
}
// @Test
public void testJWToken() throws Exception {
Secret secret = new JWTSecret("");
User user = secret.getUser();
String username = user.getUsername();
String surnameName = user.getFullName();
String nameSurname = user.getFullName(true);
logger.debug("{} - {} - {}", username, surnameName, nameSurname);
}
}

3
src/test/resources/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/logback-test.xml
/token.properties
/config.ini