Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TASK-5979 - Automatize validation of SSO with CAS for OpenCGA Enterprise 2.x.x #2461

Merged
merged 13 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing param descriptions

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's automatically generated. I know we need to change those description values :S

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What I meant was that you should add these descriptions with "@Datafield" in the "Organization configuration"

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();
j-coll marked this conversation as resolved.
Show resolved Hide resolved
}
}
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
Loading