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-6013 - Extend User Management capabilities #2432

Merged
merged 29 commits into from
Jul 5, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
bec0ed5
catalog: implement update user information for admins, #TASK-6013
pfurio Apr 17, 2024
4b7c935
catalog: implement ban option after several attempts, #TASK-6013
pfurio Apr 18, 2024
3da2c67
app: add migration script, #TASK-6013
pfurio Apr 19, 2024
af2bb26
catalog: fix compilation issues, #TASK-6013
pfurio Apr 19, 2024
7687a08
Merge branch 'develop' into TASK-6013
pfurio Apr 19, 2024
8d3fcc0
app: fix target migration version, #TASK-6013
pfurio Apr 19, 2024
49987a6
catalog: fix default expirationDate, #TASK-6013
pfurio Apr 23, 2024
240badc
Merge branch 'develop' into TASK-6013
pfurio Apr 23, 2024
216628d
catalog: add a variable with the max. login attempts, #TASK-6013
pfurio Apr 23, 2024
3e238e8
catalog: reset login attempts if it was not already 0, #TASK-6013
pfurio Apr 23, 2024
d1e95fd
catalog: exclude non OPENCGA users from being banned, #TASK-6013
pfurio Apr 29, 2024
7a85964
catalog: fix organization Id null error, #TASK-6013
pfurio Apr 29, 2024
1f441b3
Merge branch 'develop' into TASK-6013
pfurio May 22, 2024
9d304ef
core: deprecate and remove old Configuration fields, #TASK-6013
pfurio May 22, 2024
8f33068
catalog: add maxLoginAttempts to configuration.yml file, #TASK-6013
pfurio May 22, 2024
848188b
core: add maxLoginAttempts description, #TASK-6013
pfurio May 22, 2024
a68e09a
Merge branch 'develop' into TASK-6013
pfurio May 23, 2024
9c9cfc6
app: remove catalog search configuration, #TASK-6013
pfurio May 23, 2024
3b434fa
Merge branch 'TASK-6013' of github.com:opencb/opencga into TASK-6013
pfurio May 23, 2024
2834fcf
app: remove catalog search configuration, #TASK-6013
pfurio May 23, 2024
59461f7
app: completely remove catalog search config, #TASK-6013
pfurio May 23, 2024
e9cb0a5
client: update clients, #TASK-6013
pfurio May 24, 2024
52fa900
Merge branch 'develop' into TASK-6013
pfurio Jun 6, 2024
e7f1966
Merge branch 'develop' into TASK-6013
pfurio Jun 25, 2024
a6485b6
catalog: ensure new installs have a secretKey, #TASK-6013
pfurio Jun 25, 2024
e53ef29
Merge branch 'develop' into TASK-6013
pfurio Jun 27, 2024
b57c260
catalog: pass mail configuration to AuthFactory, #TASK-6013
pfurio Jul 2, 2024
eb53ffe
Merge branch 'develop' into TASK-6013
pfurio Jul 2, 2024
978efa6
catalog: use simplifyPermissions from Org configuration, #TASK-6013
pfurio Jul 4, 2024
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 @@ -214,8 +214,6 @@ public Path isolateOpenCGA() throws IOException {
Files.createDirectories(conf);
Files.createDirectories(userHome);

catalogManagerExternalResource.getConfiguration().getAdmin().setSecretKey(null);
catalogManagerExternalResource.getConfiguration().getAdmin().setAlgorithm(null);
catalogManagerExternalResource.getConfiguration().serialize(
new FileOutputStream(conf.resolve("configuration.yml").toFile()));
InputStream inputStream = StorageManager.class.getClassLoader().getResourceAsStream("storage-configuration.yml");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.opencb.opencga.app.cli.CommandExecutor;
import org.opencb.opencga.app.cli.admin.AdminCliOptionsParser;
import org.opencb.opencga.catalog.exceptions.CatalogException;
import org.opencb.opencga.core.config.Admin;

import java.util.Collections;

Expand Down Expand Up @@ -82,9 +81,6 @@ protected void setCatalogDatabaseCredentials(String host, String prefix, String
configuration.getCatalog().getDatabase().setUser(user);
}

if (configuration.getAdmin() == null) {
configuration.setAdmin(new Admin());
}
if (StringUtils.isNotEmpty(password)) {
configuration.getCatalog().getDatabase().setPassword(password);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,11 @@
import org.opencb.commons.datastore.core.QueryOptions;
import org.opencb.commons.datastore.mongodb.MongoDataStore;
import org.opencb.opencga.app.cli.admin.AdminCliOptionsParser;
import org.opencb.opencga.catalog.auth.authentication.JwtManager;
import org.opencb.opencga.catalog.db.mongodb.MongoDBAdaptorFactory;
import org.opencb.opencga.catalog.db.mongodb.MongoDBUtils;
import org.opencb.opencga.catalog.exceptions.CatalogException;
import org.opencb.opencga.catalog.managers.CatalogManager;
import org.opencb.opencga.core.common.JacksonUtils;
import org.opencb.opencga.core.common.PasswordUtils;
import org.opencb.opencga.master.monitor.MonitorService;

import javax.ws.rs.client.Client;
Expand Down Expand Up @@ -173,19 +171,12 @@ private void install() throws CatalogException {

validateConfiguration(commandOptions);

this.configuration.getAdmin().setAlgorithm("HS256");

this.configuration.getAdmin().setSecretKey(commandOptions.secretKey);
if (StringUtils.isEmpty(configuration.getAdmin().getSecretKey())) {
configuration.getAdmin().setSecretKey(PasswordUtils.getStrongRandomPassword(JwtManager.SECRET_KEY_MIN_LENGTH));
}

if (StringUtils.isEmpty(commandOptions.commonOptions.adminPassword)) {
throw new CatalogException("No admin password found. Please, insert your password.");
}

try (CatalogManager catalogManager = new CatalogManager(configuration)) {
catalogManager.installCatalogDB("HS256", configuration.getAdmin().getSecretKey(), commandOptions.commonOptions.adminPassword,
catalogManager.installCatalogDB("HS256", commandOptions.secretKey, commandOptions.commonOptions.adminPassword,
commandOptions.email, commandOptions.force);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package org.opencb.opencga.app.migrations.v3_1_0;

import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Projections;
import com.mongodb.client.model.UpdateOneModel;
import com.mongodb.client.model.Updates;
import org.bson.Document;
import org.opencb.opencga.catalog.db.mongodb.OrganizationMongoDBAdaptorFactory;
import org.opencb.opencga.catalog.migration.Migration;
import org.opencb.opencga.catalog.migration.MigrationTool;
import org.opencb.opencga.catalog.utils.Constants;
import org.opencb.opencga.core.common.TimeUtils;

@Migration(id = "addFailedLoginAttemtsMigration", description = "Add failedAttempts to User #TASK-6013", version = "3.2.0",
language = Migration.MigrationLanguage.JAVA, domain = Migration.MigrationDomain.CATALOG, date = 20240419)
public class UserBanMigration extends MigrationTool {

@Override
protected void run() throws Exception {
String lastModified = TimeUtils.getTime();
migrateCollection(OrganizationMongoDBAdaptorFactory.USER_COLLECTION,
Filters.exists("internal.registrationDate", false),
Projections.include("_id", "internal", "account"),
(document, bulk) -> {
Document internal = document.get("internal", Document.class);
Document account = document.get("account", Document.class);
internal.put("failedAttempts", 0);
internal.put("registrationDate", account.get("creationDate"));
internal.put("lastModified", lastModified);
account.put("expirationDate", Constants.DEFAULT_USER_EXPIRATION_DATE);

bulk.add(new UpdateOneModel<>(Filters.eq("_id", document.get("_id")),
Updates.combine(
Updates.set("internal", internal),
Updates.set("account", account))
)
);
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,15 @@ enum QueryParams implements QueryParam {
NAME("name", TEXT_ARRAY, ""),
EMAIL("email", TEXT_ARRAY, ""),
ORGANIZATION("organization", TEXT_ARRAY, ""),
INTERNAL("internal", OBJECT, ""),
INTERNAL_FAILED_ATTEMPTS("internal.failedAttempts", INTEGER, ""),
INTERNAL_STATUS_ID("internal.status.id", TEXT, ""),
INTERNAL_STATUS_DATE("internal.status.date", TEXT, ""),
ACCOUNT("account", TEXT_ARRAY, ""),
ACCOUNT_AUTHENTICATION_ID("account.authentication.id", TEXT, ""),
ACCOUNT_CREATION_DATE("account.creationDate", TEXT, ""),
SIZE("size", INTEGER_ARRAY, ""),
QUOTA("quota", INTEGER_ARRAY, ""),
ACCOUNT_EXPIRATION_DATE("account.expirationDate", TEXT, ""),
QUOTA("quota", OBJECT, ""),
ATTRIBUTES("attributes", TEXT, ""), // "Format: <key><operation><stringValue> where <operation> is [<|<=|>|>=|==|!=|~|!~]"

PROJECTS("projects", TEXT_ARRAY, ""),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,6 @@ private MongoDBIterator<Document> getMongoCursor(ClientSession clientSession, Qu
}
qOptions = filterQueryOptionsToIncludeKeys(qOptions,
OrganizationManager.INCLUDE_ORGANIZATION_IDS.getAsStringList(QueryOptions.INCLUDE));
if (!qOptions.getBoolean(IS_ORGANIZATION_ADMIN_OPTION)) {
qOptions = filterQueryOptionsToExcludeKeys(qOptions, Arrays.asList(QueryParams.CONFIGURATION.key()));
}

return organizationCollection.iterator(clientSession, new Document(), null, null, qOptions);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -427,27 +427,35 @@ public OpenCGAResult nativeGet(Query query, QueryOptions options) throws Catalog

@Override
public OpenCGAResult update(Query query, ObjectMap parameters, QueryOptions queryOptions) throws CatalogDBException {
Map<String, Object> userParameters = new HashMap<>();
UpdateDocument document = new UpdateDocument();

final String[] acceptedParams = {QueryParams.NAME.key(), QueryParams.EMAIL.key()};
filterStringParams(parameters, userParameters, acceptedParams);
final String[] acceptedParams = {QueryParams.NAME.key(), QueryParams.EMAIL.key(), ACCOUNT_EXPIRATION_DATE.key()};
filterStringParams(parameters, document.getSet(), acceptedParams);

if (parameters.containsKey(QueryParams.INTERNAL_STATUS_ID.key())) {
userParameters.put(QueryParams.INTERNAL_STATUS_ID.key(), parameters.get(QueryParams.INTERNAL_STATUS_ID.key()));
userParameters.put(QueryParams.INTERNAL_STATUS_DATE.key(), TimeUtils.getTime());
document.getSet().put(QueryParams.INTERNAL_STATUS_ID.key(), parameters.get(QueryParams.INTERNAL_STATUS_ID.key()));
document.getSet().put(QueryParams.INTERNAL_STATUS_DATE.key(), TimeUtils.getTime());
}

final String[] acceptedLongParams = {QueryParams.QUOTA.key(), QueryParams.SIZE.key()};
filterLongParams(parameters, userParameters, acceptedLongParams);
final String[] acceptedIntParams = {INTERNAL_FAILED_ATTEMPTS.key()};
filterIntParams(parameters, document.getSet(), acceptedIntParams);

final String[] acceptedObjectParams = {QueryParams.QUOTA.key()};
filterObjectParams(parameters, document.getSet(), acceptedObjectParams);

final String[] acceptedMapParams = {QueryParams.ATTRIBUTES.key()};
filterMapParams(parameters, userParameters, acceptedMapParams);
filterMapParams(parameters, document.getSet(), acceptedMapParams);

if (!document.toFinalUpdateDocument().isEmpty()) {
document.getSet().put(INTERNAL_LAST_MODIFIED, TimeUtils.getTime());
}

if (!userParameters.isEmpty()) {
return new OpenCGAResult(userCollection.update(parseQuery(query), new Document("$set", userParameters), null));
Document userUpdate = document.toFinalUpdateDocument();
if (userUpdate.isEmpty()) {
throw new CatalogDBException("Nothing to be updated.");
}

return OpenCGAResult.empty();
return new OpenCGAResult(userCollection.update(parseQuery(query), userUpdate, null));
}

@Override
Expand Down Expand Up @@ -476,11 +484,7 @@ public OpenCGAResult update(String userId, ObjectMap parameters)
throws CatalogDBException, CatalogParameterException, CatalogAuthorizationException {
checkId(userId);
Query query = new Query(QueryParams.ID.key(), userId);
OpenCGAResult update = update(query, parameters, QueryOptions.empty());
if (update.getNumUpdated() != 1) {
throw new CatalogDBException("Could not update user " + userId);
}
return update;
return update(query, parameters, QueryOptions.empty());
}

OpenCGAResult setStatus(Query query, String status) throws CatalogDBException {
Expand Down Expand Up @@ -666,8 +670,6 @@ private Bson parseQuery(Query query) throws CatalogDBException {
case EMAIL:
case ORGANIZATION:
case INTERNAL_STATUS_DATE:
case SIZE:
case QUOTA:
case ACCOUNT_AUTHENTICATION_ID:
case ACCOUNT_CREATION_DATE:
case TOOL_ID:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ public static CatalogAuthenticationException incorrectUserOrPassword(String doma
return new CatalogAuthenticationException(domain + ": Incorrect user or password.", e);
}

public static CatalogAuthenticationException userIsBanned(String userId) {
return new CatalogAuthenticationException("Too many login attempts. The account for user '" + userId + "' is banned."
+ " Please, talk to your organization owner/administrator.");
}

public static CatalogAuthenticationException userIsSuspended(String userId) {
return new CatalogAuthenticationException("The account for user '" + userId + "' is suspended. Please, talk to your organization"
+ " owner/administrator.");
}

public static CatalogAuthenticationException accountIsExpired(String userId, String expirationDate) {
return new CatalogAuthenticationException("The account for user '" + userId + "' expired on " + expirationDate + ". Please,"
+ " talk to your organization owner/administrator.");
}

public static CatalogAuthenticationException userNotAllowed(String domain) {
return new CatalogAuthenticationException(domain + ": User not allowed to access the system.");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import org.opencb.opencga.catalog.io.CatalogIOManager;
import org.opencb.opencga.catalog.io.IOManagerFactory;
import org.opencb.opencga.catalog.migration.MigrationManager;
import org.opencb.opencga.catalog.utils.Constants;
import org.opencb.opencga.catalog.utils.JwtUtils;
import org.opencb.opencga.catalog.utils.ParamUtils;
import org.opencb.opencga.core.common.PasswordUtils;
Expand Down Expand Up @@ -267,7 +268,7 @@ private void privateInstall(String algorithm, String secretKey, String password,

OrganizationConfiguration organizationConfiguration = new OrganizationConfiguration(
Collections.singletonList(CatalogAuthenticationManager.createOpencgaAuthenticationOrigin()),
new Optimizations(), new TokenConfiguration(algorithm, secretKey, 3600L));
Constants.DEFAULT_USER_EXPIRATION_DATE, new Optimizations(), new TokenConfiguration(algorithm, secretKey, 3600L));
organizationManager.create(new OrganizationCreateParams(ADMIN_ORGANIZATION, ADMIN_ORGANIZATION, null, null,
organizationConfiguration, null),
QueryOptions.empty(), null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.opencb.opencga.catalog.exceptions.CatalogIOException;
import org.opencb.opencga.catalog.exceptions.CatalogParameterException;
import org.opencb.opencga.catalog.io.CatalogIOManager;
import org.opencb.opencga.catalog.utils.Constants;
import org.opencb.opencga.catalog.utils.ParamUtils;
import org.opencb.opencga.catalog.utils.UuidUtils;
import org.opencb.opencga.core.api.ParamConstants;
Expand All @@ -29,10 +30,13 @@
import org.opencb.opencga.core.models.common.Enums;
import org.opencb.opencga.core.models.common.InternalStatus;
import org.opencb.opencga.core.models.organizations.*;
import org.opencb.opencga.core.models.user.OrganizationUserUpdateParams;
import org.opencb.opencga.core.models.user.User;
import org.opencb.opencga.core.response.OpenCGAResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.*;

public class OrganizationManager extends AbstractManager {
Expand Down Expand Up @@ -309,6 +313,69 @@ public OpenCGAResult<Organization> update(String organizationId, OrganizationUpd
return result;
}

public OpenCGAResult<User> updateUser(@Nullable String organizationId, String userId, OrganizationUserUpdateParams updateParams,
QueryOptions options, String token) throws CatalogException {
JwtPayload tokenPayload = catalogManager.getUserManager().validateToken(token);

ObjectMap auditParams = new ObjectMap()
.append("organizationId", organizationId)
.append("userId", userId)
.append("updateParams", updateParams)
.append("options", options)
.append("token", token);

options = ParamUtils.defaultObject(options, QueryOptions::new);
String myOrganizationId = StringUtils.isNotEmpty(organizationId) ? organizationId : tokenPayload.getOrganization();
try {
authorizationManager.checkIsAtLeastOrganizationOwnerOrAdmin(myOrganizationId, tokenPayload.getUserId(myOrganizationId));
ParamUtils.checkObj(updateParams, "OrganizationUserUpdateParams");
getUserDBAdaptor(myOrganizationId).checkId(userId);

if (StringUtils.isNotEmpty(updateParams.getEmail())) {
ParamUtils.checkEmail(updateParams.getEmail());
}
if (updateParams.getQuota() != null) {
if (updateParams.getQuota().getDiskUsage() < 0) {
throw new CatalogException("Disk usage cannot be negative");
}
if (updateParams.getQuota().getCpuUsage() < 0) {
throw new CatalogException("CPU usage cannot be negative");
}
if (updateParams.getQuota().getMaxDisk() < 0) {
throw new CatalogException("Max disk cannot be negative");
}
if (updateParams.getQuota().getMaxCpu() < 0) {
throw new CatalogException("Max CPU cannot be negative");
}
}
if (updateParams.getAccount() != null && StringUtils.isNotEmpty(updateParams.getAccount().getExpirationDate())) {
ParamUtils.checkDateIsNotExpired(updateParams.getAccount().getExpirationDate(), "expirationDate");
}

ObjectMap updateMap;
try {
updateMap = updateParams.getUpdateMap();
} catch (JsonProcessingException e) {
throw new CatalogException("Could not parse OrganizationUserUpdateParams object: " + e.getMessage(), e);
}
OpenCGAResult<User> updateResult = getUserDBAdaptor(myOrganizationId).update(userId, updateMap);
auditManager.auditUpdate(myOrganizationId, tokenPayload.getUserId(myOrganizationId), Enums.Resource.USER, userId, "", "", "",
auditParams, new AuditRecord.Status(AuditRecord.Status.Result.SUCCESS));

if (options.getBoolean(ParamConstants.INCLUDE_RESULT_PARAM)) {
// Fetch updated user
OpenCGAResult<User> result = getUserDBAdaptor(myOrganizationId).get(userId, options);
updateResult.setResults(result.getResults());
}

return updateResult;
} catch (CatalogException e) {
auditManager.auditUpdate(myOrganizationId, tokenPayload.getUserId(myOrganizationId), Enums.Resource.USER, userId, "", "", "",
auditParams, new AuditRecord.Status(AuditRecord.Status.Result.ERROR, e.getError()));
throw e;
}
}

private void validateOrganizationForCreation(Organization organization, String userId) throws CatalogParameterException {
ParamUtils.checkParameter(organization.getId(), OrganizationDBAdaptor.QueryParams.ID.key());

Expand Down Expand Up @@ -340,6 +407,8 @@ private void validateOrganizationForCreation(Organization organization, String u
|| StringUtils.isEmpty(organization.getConfiguration().getToken().getSecretKey())) {
organization.getConfiguration().setToken(TokenConfiguration.init());
}
organization.getConfiguration().setDefaultUserExpirationDate(ParamUtils.defaultString(
organization.getConfiguration().getDefaultUserExpirationDate(), Constants.DEFAULT_USER_EXPIRATION_DATE));
organization.setAttributes(ParamUtils.defaultObject(organization.getAttributes(), HashMap::new));
}

Expand Down
Loading
Loading