Skip to content

Commit

Permalink
Merge pull request #2461 from opencb/TASK-5979
Browse files Browse the repository at this point in the history
TASK-5979 - Automatize validation of SSO with CAS for OpenCGA Enterprise 2.x.x
  • Loading branch information
pfurio authored Jul 1, 2024
2 parents faa3137 + d814436 commit de0568c
Show file tree
Hide file tree
Showing 24 changed files with 795 additions and 113 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public abstract class OpenCgaCompleter implements Completer {
.map(Candidate::new)
.collect(toList());

private List<Candidate> organizationsList = asList( "create","notes-create","notes-search","notes-delete","notes-update","info","update")
private List<Candidate> organizationsList = asList( "create","notes-create","notes-search","notes-delete","notes-update","configuration-update","info","update")
.stream()
.map(Candidate::new)
.collect(toList());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ public OpencgaCliOptionsParser() {
organizationsSubCommands.addCommand("notes-search", organizationsCommandOptions.searchNotesCommandOptions);
organizationsSubCommands.addCommand("notes-delete", organizationsCommandOptions.deleteNotesCommandOptions);
organizationsSubCommands.addCommand("notes-update", organizationsCommandOptions.updateNotesCommandOptions);
organizationsSubCommands.addCommand("configuration-update", organizationsCommandOptions.updateConfigurationCommandOptions);
organizationsSubCommands.addCommand("info", organizationsCommandOptions.infoCommandOptions);
organizationsSubCommands.addCommand("update", organizationsCommandOptions.updateCommandOptions);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import org.opencb.opencga.app.cli.main.options.OrganizationsCommandOptions;
import org.opencb.opencga.catalog.exceptions.CatalogAuthenticationException;
import org.opencb.opencga.catalog.utils.ParamUtils.AddRemoveAction;
import org.opencb.opencga.catalog.utils.ParamUtils.UpdateAction;
import org.opencb.opencga.client.exceptions.ClientException;
import org.opencb.opencga.core.common.JacksonUtils;
import org.opencb.opencga.core.config.Optimizations;
Expand Down Expand Up @@ -75,6 +76,9 @@ public void execute() throws Exception {
case "notes-update":
queryResponse = updateNotes();
break;
case "configuration-update":
queryResponse = updateConfiguration();
break;
case "info":
queryResponse = info();
break;
Expand Down Expand Up @@ -223,6 +227,41 @@ private RestResponse<Note> updateNotes() throws Exception {
return openCGAClient.getOrganizationClient().updateNotes(commandOptions.id, noteUpdateParams, queryParams);
}

private RestResponse<OrganizationConfiguration> updateConfiguration() throws Exception {
logger.debug("Executing updateConfiguration in Organizations command line");

OrganizationsCommandOptions.UpdateConfigurationCommandOptions commandOptions = organizationsCommandOptions.updateConfigurationCommandOptions;

ObjectMap queryParams = new ObjectMap();
queryParams.putIfNotEmpty("include", commandOptions.include);
queryParams.putIfNotEmpty("exclude", commandOptions.exclude);
queryParams.putIfNotNull("includeResult", commandOptions.includeResult);
queryParams.putIfNotNull("authenticationOriginsAction", commandOptions.authenticationOriginsAction);


OrganizationConfiguration organizationConfiguration = null;
if (commandOptions.jsonDataModel) {
RestResponse<OrganizationConfiguration> res = new RestResponse<>();
res.setType(QueryType.VOID);
PrintUtils.println(getObjectAsJSON(categoryName,"/{apiVersion}/organizations/{organization}/configuration/update"));
return res;
} else if (commandOptions.jsonFile != null) {
organizationConfiguration = JacksonUtils.getDefaultObjectMapper()
.readValue(new java.io.File(commandOptions.jsonFile), OrganizationConfiguration.class);
} else {
ObjectMap beanParams = new ObjectMap();
putNestedIfNotNull(beanParams, "optimizations.simplifyPermissions",commandOptions.optimizationsSimplifyPermissions, true);
putNestedIfNotEmpty(beanParams, "token.algorithm",commandOptions.tokenAlgorithm, true);
putNestedIfNotEmpty(beanParams, "token.secretKey",commandOptions.tokenSecretKey, true);
putNestedIfNotNull(beanParams, "token.expiration",commandOptions.tokenExpiration, true);

organizationConfiguration = JacksonUtils.getDefaultObjectMapper().copy()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true)
.readValue(beanParams.toJson(), OrganizationConfiguration.class);
}
return openCGAClient.getOrganizationClient().updateConfiguration(commandOptions.organization, organizationConfiguration, queryParams);
}

private RestResponse<Organization> info() throws Exception {
logger.debug("Executing info in Organizations command line");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class OrganizationsCommandOptions {
public SearchNotesCommandOptions searchNotesCommandOptions;
public DeleteNotesCommandOptions deleteNotesCommandOptions;
public UpdateNotesCommandOptions updateNotesCommandOptions;
public UpdateConfigurationCommandOptions updateConfigurationCommandOptions;
public InfoCommandOptions infoCommandOptions;
public UpdateCommandOptions updateCommandOptions;

Expand All @@ -51,6 +52,7 @@ public OrganizationsCommandOptions(CommonCommandOptions commonCommandOptions, JC
this.searchNotesCommandOptions = new SearchNotesCommandOptions();
this.deleteNotesCommandOptions = new DeleteNotesCommandOptions();
this.updateNotesCommandOptions = new UpdateNotesCommandOptions();
this.updateConfigurationCommandOptions = new UpdateConfigurationCommandOptions();
this.infoCommandOptions = new InfoCommandOptions();
this.updateCommandOptions = new UpdateCommandOptions();

Expand Down Expand Up @@ -216,6 +218,47 @@ public class UpdateNotesCommandOptions {

}

@Parameters(commandNames = {"configuration-update"}, commandDescription ="Update the Organization configuration attributes")
public class UpdateConfigurationCommandOptions {

@ParametersDelegate
public CommonCommandOptions commonOptions = commonCommandOptions;

@Parameter(names = {"--json-file"}, description = "File with the body data in JSON format. Note, that using this parameter will ignore all the other parameters.", required = false, arity = 1)
public String jsonFile;

@Parameter(names = {"--json-data-model"}, description = "Show example of file structure for body data.", help = true, arity = 0)
public Boolean jsonDataModel = false;

@Parameter(names = {"--include", "-I"}, description = "Fields included in the response, whole JSON path must be provided", required = false, arity = 1)
public String include;

@Parameter(names = {"--exclude", "-E"}, description = "Fields excluded in the response, whole JSON path must be provided", required = false, arity = 1)
public String exclude;

@Parameter(names = {"--organization"}, description = "Organization id", required = true, arity = 1)
public String organization;

@Parameter(names = {"--include-result"}, description = "Flag indicating to include the created or updated document result in the response", required = false, help = true, arity = 0)
public boolean includeResult = false;

@Parameter(names = {"--authentication-origins-action"}, description = "Action to be performed if the array of authenticationOrigins is being updated.", required = false, arity = 1)
public String authenticationOriginsAction = "ADD";

@Parameter(names = {"--optimizations-simplify-permissions"}, description = "The body web service simplifyPermissions parameter", required = false, help = true, arity = 0)
public boolean optimizationsSimplifyPermissions = false;

@Parameter(names = {"--token-algorithm"}, description = "The body web service algorithm parameter", required = false, arity = 1)
public String tokenAlgorithm;

@Parameter(names = {"--token-secret-key"}, description = "The body web service secretKey parameter", required = false, arity = 1)
public String tokenSecretKey;

@Parameter(names = {"--token-expiration"}, description = "The body web service expiration parameter", required = false, arity = 1)
public Long tokenExpiration;

}

@Parameters(commandNames = {"info"}, commandDescription ="Return the organization information")
public class InfoCommandOptions {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import org.slf4j.LoggerFactory;

import javax.crypto.spec.SecretKeySpec;
import java.io.Closeable;
import java.security.Key;
import java.util.Collections;
import java.util.Date;
Expand All @@ -37,7 +38,7 @@
/**
* @author Jacobo Coll &lt;[email protected]&gt;
*/
public abstract class AuthenticationManager {
public abstract class AuthenticationManager implements Closeable {

protected JwtManager jwtManager;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,15 @@ public AzureADAuthenticationManager(AuthenticationOrigin authenticationOrigin) t
Configurator.setLevel("com.microsoft.aad.adal4j.AuthenticationAuthority", Level.WARN);
}

public static void validateAuthenticationOriginConfiguration(AuthenticationOrigin authenticationOrigin) throws CatalogException {
if (authenticationOrigin.getType() != AuthenticationOrigin.AuthenticationType.AzureAD) {
throw new CatalogException("Unknown authentication type. Expected type '" + AuthenticationOrigin.AuthenticationType.AzureAD
+ "' but received '" + authenticationOrigin.getType() + "'.");
}
AzureADAuthenticationManager azureADAuthenticationManager = new AzureADAuthenticationManager(authenticationOrigin);
azureADAuthenticationManager.close();
}

private OIDCProviderMetadata getProviderMetadata(String host) throws IOException, ParseException {
URL providerConfigurationURL = new URL(host);
InputStream stream = providerConfigurationURL.openStream();
Expand Down Expand Up @@ -420,4 +429,8 @@ public String createNonExpiringToken(String organizationId, String userId, Map<S
throw new UnsupportedOperationException("Tokens are generated by Azure via authorization code or user-password");
}

@Override
public void close() {
THREAD_POOL.shutdown();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,17 @@ public CatalogAuthenticationManager(DBAdaptorFactory dbAdaptorFactory, Email ema
this.logger = LoggerFactory.getLogger(CatalogAuthenticationManager.class);
}

public static void validateAuthenticationOriginConfiguration(AuthenticationOrigin authenticationOrigin) throws CatalogException {
if (!OPENCGA.equals(authenticationOrigin.getId())) {
throw new CatalogException("Unknown authentication origin. Expected origin id '" + OPENCGA + "' but received '"
+ authenticationOrigin.getId() + "'.");
}
if (authenticationOrigin.getType() != AuthenticationOrigin.AuthenticationType.OPENCGA) {
throw new CatalogException("Unknown authentication type. Expected type '" + AuthenticationOrigin.AuthenticationType.OPENCGA
+ "' but received '" + authenticationOrigin.getType() + "'.");
}
}

@Override
public AuthenticationResponse authenticate(String organizationId, String userId, String password)
throws CatalogAuthenticationException {
Expand Down Expand Up @@ -102,12 +113,12 @@ public void newPassword(String organizationId, String userId, String newPassword

@Override
public String createToken(String organizationId, String userId, Map<String, Object> claims, long expiration) {
return jwtManager.createJWTToken(organizationId, userId, claims, expiration);
return jwtManager.createJWTToken(organizationId, AuthenticationOrigin.AuthenticationType.OPENCGA, userId, claims, expiration);
}

@Override
public String createNonExpiringToken(String organizationId, String userId, Map<String, Object> claims) {
return jwtManager.createJWTToken(organizationId, userId, claims, 0L);
return jwtManager.createJWTToken(organizationId, AuthenticationOrigin.AuthenticationType.OPENCGA, userId, claims, 0L);
}

@Override
Expand Down Expand Up @@ -145,4 +156,8 @@ public static AuthenticationOrigin createOpencgaAuthenticationOrigin() {
.setId(CatalogAuthenticationManager.OPENCGA)
.setType(AuthenticationOrigin.AuthenticationType.OPENCGA);
}

@Override
public void close() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import io.jsonwebtoken.*;
import org.opencb.opencga.catalog.exceptions.CatalogAuthenticationException;
import org.opencb.opencga.core.config.AuthenticationOrigin;
import org.opencb.opencga.core.models.JwtPayload;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -29,6 +30,8 @@
import java.util.List;
import java.util.Map;

import static org.opencb.opencga.core.models.JwtPayload.AUTH_ORIGIN;

public class JwtManager {

private SignatureAlgorithm algorithm;
Expand Down Expand Up @@ -84,13 +87,18 @@ public JwtManager setPublicKey(Key publicKey) {
return this;
}

public String createJWTToken(String organizationId, String userId, Map<String, Object> claims, long expiration) {
public String createJWTToken(String organizationId, AuthenticationOrigin.AuthenticationType type, String userId,
Map<String, Object> claims, long expiration) {
long currentTime = System.currentTimeMillis();

JwtBuilder jwtBuilder = Jwts.builder();
if (claims != null && !claims.isEmpty()) {
jwtBuilder.setClaims(claims);
}
if (type != null) {
jwtBuilder.addClaims(Collections.singletonMap(AUTH_ORIGIN, type));
}

jwtBuilder.setSubject(userId)
.setAudience(organizationId)
.setIssuer("OpenCGA")
Expand All @@ -115,14 +123,24 @@ public void validateToken(String token, Key publicKey) throws CatalogAuthenticat

public JwtPayload getPayload(String token) throws CatalogAuthenticationException {
Claims body = parseClaims(token, publicKey).getBody();
return new JwtPayload(body.getSubject(), body.getAudience(), body.getIssuer(), body.getIssuedAt(), body.getExpiration(), token);
return new JwtPayload(body.getSubject(), body.getAudience(), getAuthOrigin(body), body.getIssuer(), body.getIssuedAt(),
body.getExpiration(), token);
}

public JwtPayload getPayload(String token, Key publicKey) throws CatalogAuthenticationException {
Claims body = parseClaims(token, publicKey).getBody();
return new JwtPayload(body.getSubject(), body.getAudience(), body.getIssuer(), body.getIssuedAt(), body.getExpiration(), token);
return new JwtPayload(body.getSubject(), body.getAudience(), getAuthOrigin(body), body.getIssuer(), body.getIssuedAt(),
body.getExpiration(), token);
}

private AuthenticationOrigin.AuthenticationType getAuthOrigin(Claims claims) {
String o = claims.get(AUTH_ORIGIN, String.class);
if (o != null) {
return AuthenticationOrigin.AuthenticationType.valueOf(o);
} else {
return null;
}
}

public String getAudience(String token) throws CatalogAuthenticationException {
return getAudience(token, this.publicKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
import org.opencb.commons.datastore.core.ObjectMap;
import org.opencb.opencga.catalog.exceptions.CatalogAuthenticationException;
import org.opencb.opencga.catalog.exceptions.CatalogException;
import org.opencb.opencga.catalog.utils.ParamUtils;
import org.opencb.opencga.core.common.TimeUtils;
import org.opencb.opencga.core.config.AuthenticationOrigin;
import org.opencb.opencga.core.models.organizations.TokenConfiguration;
import org.opencb.opencga.core.models.user.*;
import org.opencb.opencga.core.response.OpenCGAResult;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -124,6 +126,24 @@ protected static String envToStringRedacted(Hashtable<String, Object> env) {
return string;
}

public static void validateAuthenticationOriginConfiguration(AuthenticationOrigin authenticationOrigin) throws CatalogException {
if (authenticationOrigin.getType() != AuthenticationType.LDAP) {
throw new CatalogException("Unknown authentication type. Expected type '" + AuthenticationType.LDAP + "' but received '"
+ authenticationOrigin.getType() + "'.");
}
ParamUtils.checkParameter(authenticationOrigin.getHost(), AuthenticationType.LDAP + " host.");

TokenConfiguration defaultTokenConfig = TokenConfiguration.init();
LDAPAuthenticationManager ldapAuthenticationManager = new LDAPAuthenticationManager(authenticationOrigin,
defaultTokenConfig.getAlgorithm(), defaultTokenConfig.getSecretKey(), defaultTokenConfig.getExpiration());
DirContext dirContext = ldapAuthenticationManager.getDirContext(ldapAuthenticationManager.getDefaultEnv(), 1);
if (dirContext == null) {
throw new CatalogException("LDAP: Could not connect to the LDAP server using the provided configuration.");
}
ldapAuthenticationManager.closeDirContextAndSuppress(dirContext, new Exception());
ldapAuthenticationManager.close();
}

@Override
public AuthenticationResponse authenticate(String organizationId, String userId, String password)
throws CatalogAuthenticationException {
Expand Down Expand Up @@ -213,12 +233,12 @@ public void newPassword(String organizationId, String userId, String newPassword

@Override
public String createToken(String organizationId, String userId, Map<String, Object> claims, long expiration) {
return jwtManager.createJWTToken(organizationId, userId, claims, expiration);
return jwtManager.createJWTToken(organizationId, AuthenticationType.LDAP, userId, claims, expiration);
}

@Override
public String createNonExpiringToken(String organizationId, String userId, Map<String, Object> claims) {
return jwtManager.createJWTToken(organizationId, userId, claims, 0L);
return jwtManager.createJWTToken(organizationId, AuthenticationType.LDAP, userId, claims, 0L);
}

/* Private methods */
Expand Down Expand Up @@ -503,4 +523,9 @@ private String takeString(ObjectMap objectMap, String key, String defaultValue)
objectMap.remove(key);
return value;
}

@Override
public void close() {
executorService.shutdown();
}
}
Loading

0 comments on commit de0568c

Please sign in to comment.