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
/.classpath
/.settings
/.DS_Store

View File

@ -1,6 +1,25 @@
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]

View File

@ -13,7 +13,7 @@ N/A
## Change log
See [Releases](https://code-repo.d4science.org/gCubeSystem/gcat/releases).
See [CHANGELOG.md](CHANGELOG.md).
## 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
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
Infrastructures enabling the dynamic deployment of Virtual Research Environments

31
pom.xml
View File

@ -10,7 +10,7 @@
<groupId>org.gcube.common</groupId>
<artifactId>authorization-utils</artifactId>
<version>1.0.0</version>
<version>2.3.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -27,7 +27,7 @@
<dependency>
<groupId>org.gcube.distribution</groupId>
<artifactId>gcube-bom</artifactId>
<version>2.0.1</version>
<version>2.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
@ -62,7 +62,32 @@
<dependency>
<groupId>org.gcube.common</groupId>
<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>
</dependencies>

View File

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

View File

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

View File

@ -1,102 +1,116 @@
package org.gcube.common.authorization.utils.manager;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
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.secret.JWTSecret;
import org.gcube.common.authorization.utils.secret.Secret;
import org.gcube.common.authorization.utils.secret.SecretUtility;
import org.gcube.common.authorization.utils.user.User;
/**
* @author Luca Frosini (ISTI - CNR)
*/
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 currentSecretHolder;
private SecretManager(){
public SecretManager() {
initialSecretHolder = new SecretHolder();
currentSecretHolder = initialSecretHolder;
initSecretProviders();
}
protected void initSecretProviders() {
secretProviders = new ArrayList<>();
@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 synchronized void addSecretViaProvider(SecretProvider secretProvider) {
if (currentSecretHolder != initialSecretHolder) {
throw new RuntimeException("You can't add a Secret in a session. You must terminate the session first.");
}
}
public List<SecretProvider> getSecretProviders(){
return secretProviders;
}
public void addSecretProvider(SecretProvider secretProvider) {
secretProviders.add(secretProvider);
Secret secret = secretProvider.getSecret();
initialSecretHolder.addSecret(secret);
currentSecretHolder.addSecret(secret);
}
public synchronized void startSession(Secret secret) throws Exception {
if(currentSecretHolder!=initialSecretHolder) {
throw new Exception("You are already in a session. You must terminate the session first.");
public synchronized void addSecret(Secret secret) {
if (currentSecretHolder != initialSecretHolder) {
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.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.set();
}
public synchronized void endSession() throws Exception {
if(currentSecretHolder!=initialSecretHolder) {
initialSecretHolder.set();
public synchronized void startSession(SecretHolder secretHolder) throws Exception {
if (currentSecretHolder != initialSecretHolder) {
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;
}
}
public void reset() {
initialSecretHolder.reset();
if(initialSecretHolder!=currentSecretHolder) {
currentSecretHolder.reset();
public synchronized void set() throws Exception {
if (currentSecretHolder != initialSecretHolder) {
throw new Exception("You are in a session. You must terminate the session first.");
}
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();
}
public User getUser() {
public synchronized User 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.setRoles(new HashSet<>(clientInfo.getRoles()));
gCubeUser.setUsername(clientInfo.getId());
gCubeUser.setApplication(true);
user = gCubeUser;
break;
}

View File

@ -1,17 +1,20 @@
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.List;
import java.util.HashSet;
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.ExternalServiceInfo;
import org.gcube.common.authorization.library.provider.UserInfo;
import org.gcube.common.authorization.library.utils.Caller;
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.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);
@ -68,7 +76,7 @@ public class JWTSecret extends Secret {
if(expired && renewalProvider!=null) {
try {
JWTSecret renewed = (JWTSecret) renewalProvider.renew();
JWTSecret renewed = (JWTSecret) renewalProvider.renew(getContext());
this.token = renewed.token;
this.accessToken = getAccessToken();
}catch (Exception e) {
@ -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,71 @@ 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) {
User user = getUser();
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;
}
@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 +196,7 @@ public class JWTSecret extends Secret {
@Override
public boolean isExpired() {
return isExpired(accessToken);
return isExpired(getAccessToken());
}
@Override
@ -185,8 +217,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,12 @@ package org.gcube.common.authorization.utils.secret;
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 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.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.user.GCubeUser;
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 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 String serviceBasePath;
@ -50,7 +51,8 @@ public class SocialService {
}
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);
if(socialService == null) {
socialService = new SocialService();
@ -68,9 +70,10 @@ public class SocialService {
}
protected void getServiceBasePathViaGCoreEndpoint() throws Exception {
SecretManager secretManager = SecretManagerProvider.instance.get();
try {
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(String.format("$resource/Profile/ServiceName/text() eq '%s'", SERVICE_NAME));
query.setResult(
@ -81,20 +84,20 @@ public class SocialService {
List<String> endpoints = client.submit(query);
if(endpoints == null || endpoints.isEmpty()) {
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);
if(serviceBasePath == null)
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 + "/";
} catch(Exception e) {
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);
throw new Exception(error);
}

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,99 @@ public class KeycloakUser extends AccessToken implements User {
*/
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
@JsonIgnore
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
@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 = 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 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();

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