diff --git a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphlAdminFieldFactory.java b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphlAdminFieldFactory.java deleted file mode 100644 index 9f24bc5df9..0000000000 --- a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphlAdminFieldFactory.java +++ /dev/null @@ -1,144 +0,0 @@ -package org.molgenis.emx2.graphql; - -import static org.molgenis.emx2.Constants.MOLGENIS_JWT_SHARED_SECRET; -import static org.molgenis.emx2.Constants.SETTINGS; -import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.Status.SUCCESS; -import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.typeForMutationResult; -import static org.molgenis.emx2.graphql.GraphqlConstants.*; -import static org.molgenis.emx2.graphql.GraphqlSchemaFieldFactory.outputSettingsType; - -import graphql.Scalars; -import graphql.schema.*; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import org.molgenis.emx2.Database; -import org.molgenis.emx2.User; - -public class GraphlAdminFieldFactory { - private GraphlAdminFieldFactory() { - // hide constructor - } - - // Output types - private static GraphQLOutputType userType = - GraphQLObjectType.newObject() - .name("_AdminUserType") - .field( - GraphQLFieldDefinition.newFieldDefinition() - .name(EMAIL) - .type(Scalars.GraphQLString) - .build()) - .field( - GraphQLFieldDefinition.newFieldDefinition() - .name(ENABLED) - .type(Scalars.GraphQLBoolean) - .build()) - .field( - GraphQLFieldDefinition.newFieldDefinition() - .name(SETTINGS) - .type(GraphQLList.list(outputSettingsType)) - .build()) - .build(); - - // retrieve user list, user count - public static GraphQLFieldDefinition queryAdminField(Database db) { - GraphQLOutputType adminType = - GraphQLObjectType.newObject() - .name("MolgenisAdmin") - .field( - GraphQLFieldDefinition.newFieldDefinition() - .name(USERS) - .argument(GraphQLArgument.newArgument().name(EMAIL).type(Scalars.GraphQLString)) - .argument(GraphQLArgument.newArgument().name(LIMIT).type(Scalars.GraphQLInt)) - .argument(GraphQLArgument.newArgument().name(OFFSET).type(Scalars.GraphQLInt)) - .type(GraphQLList.list(userType)) - .build()) - .field( - GraphQLFieldDefinition.newFieldDefinition() - .name("userCount") - .type(Scalars.GraphQLInt) - .build()) - .build(); - - return GraphQLFieldDefinition.newFieldDefinition() - .name("_admin") - .dataFetcher( - dataFetchingEnvironment -> { - Map result = new LinkedHashMap<>(); - // check for parameters - for (SelectedField selectedField : - dataFetchingEnvironment.getSelectionSet().getImmediateFields()) { - if (selectedField.getName().equals(USERS)) { - result.put(USERS, getUsers(selectedField, db)); - } - if (selectedField.getName().equals("userCount")) { - result.put("userCount", db.countUsers()); - } - } - return result; - }) - .type(adminType) - .build(); - } - - private static Object getUsers(SelectedField selectedField, Database db) { - Map args = selectedField.getArguments(); - int limit = args.containsKey(LIMIT) ? (int) args.get(LIMIT) : 100; - int offset = args.containsKey(OFFSET) ? (int) args.get(OFFSET) : 0; - String email = args.containsKey(EMAIL) ? (String) args.get(EMAIL) : null; - if (email != null) { - return List.of(toGraphqlUser(db.getUser(email))); - } else { - return db.getUsers(limit, offset).stream() - .map(GraphlAdminFieldFactory::toGraphqlUser) - .toList(); - } - } - - public static GraphQLFieldDefinition removeUser(Database database) { - return GraphQLFieldDefinition.newFieldDefinition() - .name("removeUser") - .type(typeForMutationResult) - .argument(GraphQLArgument.newArgument().name(EMAIL).type(Scalars.GraphQLString)) - .dataFetcher( - dataFetchingEnvironment -> { - String email = dataFetchingEnvironment.getArgument(EMAIL); - database.removeUser(email); - return new GraphqlApiMutationResult(SUCCESS, "User %s removed", email); - }) - .build(); - } - - public static GraphQLFieldDefinition setEnabledUser(Database database) { - return GraphQLFieldDefinition.newFieldDefinition() - .name("setEnabledUser") - .type(typeForMutationResult) - .argument(GraphQLArgument.newArgument().name(EMAIL).type(Scalars.GraphQLString)) - .argument(GraphQLArgument.newArgument().name(ENABLED).type(Scalars.GraphQLBoolean)) - .dataFetcher( - dataFetchingEnvironment -> { - String email = dataFetchingEnvironment.getArgument(EMAIL); - Boolean enabled = dataFetchingEnvironment.getArgument(ENABLED); - database.setEnabledUser(email, enabled); - return new GraphqlApiMutationResult( - SUCCESS, "User %s %s ", email, enabled ? "Enabled" : "Disabled"); - }) - .build(); - } - - private static Map toGraphqlUser(User user) { - Map result = new LinkedHashMap<>(); - result.put(EMAIL, user.getUsername()); - result.put(ENABLED, user.getEnabled()); - result.put(SETTINGS, mapSettingsToGraphql(user.getSettings())); - return result; - } - - public static Object mapSettingsToGraphql(Map settings) { - return settings.entrySet().stream() - .filter(entry -> !MOLGENIS_JWT_SHARED_SECRET.equals(entry.getKey())) - .map(entry -> Map.of(KEY, entry.getKey(), VALUE, entry.getValue())) - .toList(); - } -} diff --git a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlAdminFieldFactory.java b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlAdminFieldFactory.java new file mode 100644 index 0000000000..ec26bc9e94 --- /dev/null +++ b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlAdminFieldFactory.java @@ -0,0 +1,248 @@ +package org.molgenis.emx2.graphql; + +import static org.molgenis.emx2.Constants.*; +import static org.molgenis.emx2.Constants.KEY; +import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.Status.SUCCESS; +import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.typeForMutationResult; +import static org.molgenis.emx2.graphql.GraphqlConstants.*; +import static org.molgenis.emx2.graphql.GraphqlSchemaFieldFactory.*; + +import graphql.Scalars; +import graphql.schema.*; +import java.util.*; +import org.jetbrains.annotations.NotNull; +import org.molgenis.emx2.*; + +public class GraphqlAdminFieldFactory { + private GraphqlAdminFieldFactory() { + // hide constructor + } + + private static final String UPDATE_USER = "updateUser"; + + // Output types + private static final GraphQLOutputType userType = + GraphQLObjectType.newObject() + .name("_AdminUserType") + .field( + GraphQLFieldDefinition.newFieldDefinition() + .name(EMAIL) + .type(Scalars.GraphQLString) + .build()) + .field( + GraphQLFieldDefinition.newFieldDefinition() + .name(ENABLED) + .type(Scalars.GraphQLBoolean) + .build()) + .field( + GraphQLFieldDefinition.newFieldDefinition() + .name(SETTINGS) + .type(GraphQLList.list(outputSettingsType)) + .build()) + .field( + GraphQLFieldDefinition.newFieldDefinition() + .name(ROLES) + .type(GraphQLList.list(userRolesType)) + .build()) + .build(); + + // retrieve user list, user count + public static GraphQLFieldDefinition queryAdminField(Database db) { + String userCount = "userCount"; + GraphQLOutputType adminType = + GraphQLObjectType.newObject() + .name("MolgenisAdmin") + .field( + GraphQLFieldDefinition.newFieldDefinition() + .name(USERS) + .argument(GraphQLArgument.newArgument().name(EMAIL).type(Scalars.GraphQLString)) + .argument(GraphQLArgument.newArgument().name(LIMIT).type(Scalars.GraphQLInt)) + .argument(GraphQLArgument.newArgument().name(OFFSET).type(Scalars.GraphQLInt)) + .type(GraphQLList.list(userType)) + .build()) + .field( + GraphQLFieldDefinition.newFieldDefinition() + .name(userCount) + .type(Scalars.GraphQLInt) + .build()) + .build(); + + return GraphQLFieldDefinition.newFieldDefinition() + .name("_admin") + .dataFetcher( + dataFetchingEnvironment -> { + Map result = new LinkedHashMap<>(); + // check for parameters + for (SelectedField selectedField : + dataFetchingEnvironment.getSelectionSet().getImmediateFields()) { + if (selectedField.getName().equals(USERS)) { + result.put(USERS, getUsers(selectedField, db)); + } + if (selectedField.getName().equals(userCount)) { + result.put(userCount, db.countUsers()); + } + } + return result; + }) + .type(adminType) + .build(); + } + + private static Object getUsers(SelectedField selectedField, Database db) { + Map args = selectedField.getArguments(); + int limit = args.containsKey(LIMIT) ? (int) args.get(LIMIT) : 100; + int offset = args.containsKey(OFFSET) ? (int) args.get(OFFSET) : 0; + String email = args.containsKey(EMAIL) ? (String) args.get(EMAIL) : null; + List members = db.loadUserRoles(); + + if (email != null) { + return List.of(toGraphqlUser(db.getUser(email), members)); + } else { + return db.getUsers(limit, offset).stream().map(user -> toGraphqlUser(user, members)).toList(); + } + } + + public static GraphQLFieldDefinition removeUser(Database database) { + return GraphQLFieldDefinition.newFieldDefinition() + .name("removeUser") + .type(typeForMutationResult) + .argument(GraphQLArgument.newArgument().name(EMAIL).type(Scalars.GraphQLString)) + .dataFetcher( + dataFetchingEnvironment -> { + String email = dataFetchingEnvironment.getArgument(EMAIL); + database.removeUser(email); + return new GraphqlApiMutationResult(SUCCESS, "User %s removed", email); + }) + .build(); + } + + public static GraphQLFieldDefinition setEnabledUser(Database database) { + return GraphQLFieldDefinition.newFieldDefinition() + .name("setEnabledUser") + .type(typeForMutationResult) + .argument(GraphQLArgument.newArgument().name(EMAIL).type(Scalars.GraphQLString)) + .argument(GraphQLArgument.newArgument().name(ENABLED).type(Scalars.GraphQLBoolean)) + .dataFetcher( + dataFetchingEnvironment -> { + String email = dataFetchingEnvironment.getArgument(EMAIL); + boolean enabled = dataFetchingEnvironment.getArgument(ENABLED); + database.setEnabledUser(email, enabled); + return new GraphqlApiMutationResult( + SUCCESS, "User %s %s ", email, enabled ? "Enabled" : "Disabled"); + }) + .build(); + } + + private static Map toGraphqlUser(User user, List members) { + Map result = new LinkedHashMap<>(); + result.put(EMAIL, user.getUsername()); + result.put(ENABLED, user.getEnabled()); + result.put(SETTINGS, mapSettingsToGraphql(user.getSettings())); + + List> roles = getRoles(user, members); + result.put(ROLES, roles); + return result; + } + + private static List> getRoles(User user, List members) { + return members.stream() + .filter(member -> member.getUser().equals(user.getUsername())) + .map(GraphqlAdminFieldFactory::getUserRoleMap) + .toList(); + } + + private static Map getUserRoleMap(Member member) { + String role = member.getRole(); + String[] parts = role.split("/"); + Map roleMap = new HashMap<>(); + roleMap.put(SCHEMA_ID, parts[0]); + roleMap.put(ROLE, parts[1]); + return roleMap; + } + + public static Object mapSettingsToGraphql(Map settings) { + return settings.entrySet().stream() + .filter(entry -> !MOLGENIS_JWT_SHARED_SECRET.equals(entry.getKey())) + .map(entry -> Map.of(KEY, entry.getKey(), VALUE, entry.getValue())) + .toList(); + } + + public static GraphQLFieldDefinition updateUser(Database database) { + return GraphQLFieldDefinition.newFieldDefinition() + .name(UPDATE_USER) + .type(typeForMutationResult) + .argument(GraphQLArgument.newArgument().name(UPDATE_USER).type(updateUserType)) + .dataFetcher( + dataFetchingEnvironment -> executeUpdateUser(database, dataFetchingEnvironment)) + .build(); + } + + @NotNull + private static GraphqlApiMutationResult executeUpdateUser( + Database database, DataFetchingEnvironment dataFetchingEnvironment) { + LinkedHashMap updatedUser = dataFetchingEnvironment.getArgument(UPDATE_USER); + String userName = (String) updatedUser.get(EMAIL); + if (userName != null) { + database.tx( + db -> { + String password = (String) updatedUser.get(PASSWORD); + if (password != null) { + db.setUserPassword(userName, password); + } + + List> roles = (List>) updatedUser.get(ROLES); + if (roles != null && roles.iterator().hasNext()) { + db.updateRoles(userName, roles); + } + + List> revokedRoles = + (List>) updatedUser.get("revokedRoles"); + if (revokedRoles != null && revokedRoles.iterator().hasNext()) { + db.revokeRoles(userName, revokedRoles); + } + + Boolean enabled = (Boolean) updatedUser.get(ENABLED); + if (enabled != null) { + db.setEnabledUser(userName, enabled); + } + }); + } + return new GraphqlApiMutationResult(SUCCESS, "User %s updated", userName); + } + + private static final GraphQLInputObjectType inputUserRolesType = + new GraphQLInputObjectType.Builder() + .name("InputUserRolesType") + .field( + GraphQLInputObjectField.newInputObjectField() + .name(SCHEMA_ID) + .type(Scalars.GraphQLString)) + .field( + GraphQLInputObjectField.newInputObjectField().name(ROLE).type(Scalars.GraphQLString)) + .field( + GraphQLInputObjectField.newInputObjectField().name(USER).type(Scalars.GraphQLString)) + .build(); + + private static final GraphQLInputObjectType updateUserType = + new GraphQLInputObjectType.Builder() + .name("InputUpdateUser") + .field( + GraphQLInputObjectField.newInputObjectField().name(EMAIL).type(Scalars.GraphQLString)) + .field( + GraphQLInputObjectField.newInputObjectField() + .name(PASSWORD) + .type(Scalars.GraphQLString)) + .field( + GraphQLInputObjectField.newInputObjectField() + .name(ENABLED) + .type(Scalars.GraphQLBoolean)) + .field( + GraphQLInputObjectField.newInputObjectField() + .name(ROLES) + .type(GraphQLList.list(inputUserRolesType))) + .field( + GraphQLInputObjectField.newInputObjectField() + .name("revokedRoles") + .type(GraphQLList.list(inputUserRolesType))) + .build(); +} diff --git a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlApiFactory.java b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlApiFactory.java index 6b5e2d011f..9c0ef0a7bf 100644 --- a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlApiFactory.java +++ b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlApiFactory.java @@ -60,9 +60,10 @@ public GraphQL createGraphqlForDatabase(Database database, TaskService taskServi // admin operations if (database.isAdmin()) { - queryBuilder.field(GraphlAdminFieldFactory.queryAdminField(database)); - mutationBuilder.field(GraphlAdminFieldFactory.removeUser(database)); - mutationBuilder.field(GraphlAdminFieldFactory.setEnabledUser(database)); + queryBuilder.field(GraphqlAdminFieldFactory.queryAdminField(database)); + mutationBuilder.field(GraphqlAdminFieldFactory.removeUser(database)); + mutationBuilder.field(GraphqlAdminFieldFactory.setEnabledUser(database)); + mutationBuilder.field(GraphqlAdminFieldFactory.updateUser(database)); } // database operations diff --git a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlApiMutationResult.java b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlApiMutationResult.java index e1de90d523..511fcdef6f 100644 --- a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlApiMutationResult.java +++ b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlApiMutationResult.java @@ -21,7 +21,6 @@ public enum Status { private Status status; private String taskId; private Map details = new LinkedHashMap<>(); - private String code; public GraphqlApiMutationResult(Status status, String message, Object... formatValues) { this.status = status; diff --git a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlDatabaseFieldFactory.java b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlDatabaseFieldFactory.java index 254fda4c3c..15d0a7264b 100644 --- a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlDatabaseFieldFactory.java +++ b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlDatabaseFieldFactory.java @@ -2,7 +2,7 @@ import static org.molgenis.emx2.Constants.DESCRIPTION; import static org.molgenis.emx2.Constants.SETTINGS; -import static org.molgenis.emx2.graphql.GraphlAdminFieldFactory.mapSettingsToGraphql; +import static org.molgenis.emx2.graphql.GraphqlAdminFieldFactory.mapSettingsToGraphql; import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.Status.SUCCESS; import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.typeForMutationResult; import static org.molgenis.emx2.graphql.GraphqlConstants.*; diff --git a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlSchemaFieldFactory.java b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlSchemaFieldFactory.java index f0ae887663..1b703a6120 100644 --- a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlSchemaFieldFactory.java +++ b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlSchemaFieldFactory.java @@ -1,7 +1,7 @@ package org.molgenis.emx2.graphql; import static org.molgenis.emx2.Constants.*; -import static org.molgenis.emx2.graphql.GraphlAdminFieldFactory.mapSettingsToGraphql; +import static org.molgenis.emx2.graphql.GraphqlAdminFieldFactory.mapSettingsToGraphql; import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.Status.SUCCESS; import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.typeForMutationResult; import static org.molgenis.emx2.graphql.GraphqlConstants.*; @@ -27,7 +27,7 @@ import org.slf4j.LoggerFactory; public class GraphqlSchemaFieldFactory { - private static Logger logger = LoggerFactory.getLogger(SqlDatabase.class); + private static final Logger logger = LoggerFactory.getLogger(SqlDatabase.class); public static final GraphQLInputObjectType inputSettingsMetadataType = new GraphQLInputObjectType.Builder() @@ -124,6 +124,17 @@ public class GraphqlSchemaFieldFactory { .name(GraphqlConstants.NAME) .type(Scalars.GraphQLString)) .build(); + + static final GraphQLType userRolesType = + new GraphQLObjectType.Builder() + .name("MolgenisUserRolesType") + .field( + GraphQLFieldDefinition.newFieldDefinition() + .name(SCHEMA_ID) + .type(Scalars.GraphQLString)) + .field(GraphQLFieldDefinition.newFieldDefinition().name(ROLE).type(Scalars.GraphQLString)) + .build(); + private static final GraphQLType outputMembersMetadataType = new GraphQLObjectType.Builder() .name("MolgenisMembersType") diff --git a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlSessionFieldFactory.java b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlSessionFieldFactory.java index fc2c59dfdc..4fb9ce3f4f 100644 --- a/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlSessionFieldFactory.java +++ b/backend/molgenis-emx2-graphql/src/main/java/org/molgenis/emx2/graphql/GraphqlSessionFieldFactory.java @@ -1,7 +1,7 @@ package org.molgenis.emx2.graphql; import static org.molgenis.emx2.Constants.SETTINGS; -import static org.molgenis.emx2.graphql.GraphlAdminFieldFactory.mapSettingsToGraphql; +import static org.molgenis.emx2.graphql.GraphqlAdminFieldFactory.mapSettingsToGraphql; import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.Status.FAILED; import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.Status.SUCCESS; import static org.molgenis.emx2.graphql.GraphqlApiMutationResult.typeForMutationResult; diff --git a/backend/molgenis-emx2-graphql/src/test/java/org/molgenis/emx2/graphql/TestGraphqlAdminFields.java b/backend/molgenis-emx2-graphql/src/test/java/org/molgenis/emx2/graphql/TestGraphqlAdminFields.java index 091a069f20..84ebb1a7f3 100644 --- a/backend/molgenis-emx2-graphql/src/test/java/org/molgenis/emx2/graphql/TestGraphqlAdminFields.java +++ b/backend/molgenis-emx2-graphql/src/test/java/org/molgenis/emx2/graphql/TestGraphqlAdminFields.java @@ -1,19 +1,24 @@ package org.molgenis.emx2.graphql; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; import static org.molgenis.emx2.graphql.GraphqlApiFactory.convertExecutionResultToJson; import static org.molgenis.emx2.sql.SqlDatabase.ANONYMOUS; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import graphql.ExecutionInput; import graphql.GraphQL; + import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.molgenis.emx2.Database; -import org.molgenis.emx2.MolgenisException; -import org.molgenis.emx2.Schema; +import org.molgenis.emx2.*; import org.molgenis.emx2.sql.TestDatabaseFactory; public class TestGraphqlAdminFields { @@ -21,16 +26,16 @@ public class TestGraphqlAdminFields { private static GraphQL grapql; private static Database database; private static final String schemaName = TestGraphqlAdminFields.class.getSimpleName(); + private static final String TEST_PERSOON = "testPersoon"; + private static final String ANOTHER_SCHEMA_NAME = TestGraphqlAdminFields.class.getSimpleName() + "2"; @BeforeAll public static void setup() { database = TestDatabaseFactory.getTestDatabase(); - // PetStoreExample.create(schema.getMetadata()); - // PetStoreExample.populate(schema); } @Test - public void testUsers() { + void testUsers() { // put in transaction so user count is not affected by other operations database.tx( tdb -> { @@ -57,6 +62,81 @@ public void testUsers() { }); } + @Test + void testUpdateUser() { + database.tx( + testDatabase -> { + testDatabase.becomeAdmin(); + GraphQL graphql = new GraphqlApiFactory().createGraphqlForDatabase(testDatabase, null); + + try { + // setup + testDatabase.dropCreateSchema(schemaName); + testDatabase.dropCreateSchema(ANOTHER_SCHEMA_NAME); + testDatabase.addUser(TEST_PERSOON); + testDatabase.setEnabledUser(TEST_PERSOON, true); + testDatabase.getSchema(schemaName).addMember(TEST_PERSOON, "Owner"); + testDatabase.getSchema(ANOTHER_SCHEMA_NAME).addMember(TEST_PERSOON, "Viewer"); + + // test + String query = + "mutation updateUser($updateUser:InputUpdateUser) {updateUser(updateUser:$updateUser){status, message}}"; + Map variables = createUpdateUserVar(); + ExecutionInput build = + ExecutionInput.newExecutionInput().query(query).variables(variables).build(); + String queryResult = convertExecutionResultToJson(graphql.execute(build)); + JsonNode node = new ObjectMapper().readTree(queryResult); + if (node.get("errors") != null) { + throw new MolgenisException(node.get("errors").get(0).get("message").asText()); + } + + // assert results + User user = testDatabase.getUser(TEST_PERSOON); + assertEquals("testPersoon", user.getUsername()); + assertFalse(user.getEnabled()); + + List members = testDatabase.getSchema(schemaName).getMembers(); + assertTrue(members.isEmpty()); + + Member anotherSchemaMember = + testDatabase.getSchema(ANOTHER_SCHEMA_NAME).getMembers().stream().findFirst().get(); + assertEquals("Owner", anotherSchemaMember.getRole()); + assertEquals(TEST_PERSOON, anotherSchemaMember.getUser()); + + // clean up + testDatabase.removeUser(TEST_PERSOON); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + @NotNull + private static Map createUpdateUserVar() { + Map variables = new HashMap<>(); + Map updateUser = new HashMap<>(); + updateUser.put("email", TEST_PERSOON); + updateUser.put("password", "12345678"); + updateUser.put("enabled", "false"); + + ArrayList> revokedRoles = new ArrayList<>(); + Map revokedRole = new HashMap<>(); + revokedRole.put("schemaId", schemaName); + revokedRole.put("role", "Owner"); + revokedRoles.add(revokedRole); + updateUser.put("revokedRoles", revokedRoles); + + ArrayList> roles = new ArrayList<>(); + Map role = new HashMap<>(); + role.put("schemaId", ANOTHER_SCHEMA_NAME); + role.put("role", "Owner"); + roles.add(role); + updateUser.put("roles", roles); + + variables.put("updateUser", updateUser); + return variables; + } + private JsonNode execute(String query) throws IOException { String result = convertExecutionResultToJson(grapql.execute(query)); JsonNode node = new ObjectMapper().readTree(result); diff --git a/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/MetadataUtils.java b/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/MetadataUtils.java index 224c3e2918..13b4b68a25 100644 --- a/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/MetadataUtils.java +++ b/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/MetadataUtils.java @@ -513,9 +513,9 @@ protected static void deleteTable(DSLContext jooq, TableMetadata table) { protected static void saveColumnMetadata(DSLContext jooq, Column column) { String refSchema = column.isReference() - ? column.getRefSchemaName().equals(column.getSchemaName()) + ? (column.getRefSchemaName().equals(column.getSchemaName()) ? null - : column.getRefSchemaName() + : column.getRefSchemaName()) : null; jooq.insertInto(COLUMN_METADATA) .columns( diff --git a/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlDatabase.java b/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlDatabase.java index 1e859d2f80..2660273b4e 100644 --- a/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlDatabase.java +++ b/backend/molgenis-emx2-sql/src/main/java/org/molgenis/emx2/sql/SqlDatabase.java @@ -2,8 +2,7 @@ import static org.jooq.impl.DSL.name; import static org.molgenis.emx2.ColumnType.STRING; -import static org.molgenis.emx2.Constants.MG_USER_PREFIX; -import static org.molgenis.emx2.Constants.SYSTEM_SCHEMA; +import static org.molgenis.emx2.Constants.*; import static org.molgenis.emx2.sql.MetadataUtils.*; import static org.molgenis.emx2.sql.SqlDatabaseExecutor.*; import static org.molgenis.emx2.sql.SqlSchemaMetadataExecutor.executeCreateSchema; @@ -14,6 +13,7 @@ import java.util.function.Supplier; import javax.sql.DataSource; import org.jooq.DSLContext; +import org.jooq.Record; import org.jooq.SQLDialect; import org.jooq.conf.Settings; import org.jooq.exception.DataAccessException; @@ -805,4 +805,68 @@ public TableListener getTableListener(String schemaName, String tableName) { public List getLastUpdated() { return ChangeLogExecutor.executeLastUpdates(jooq); } + + public List loadUserRoles() { + List members = new ArrayList<>(); + String roleFilter = Constants.MG_ROLE_PREFIX; + String userFilter = Constants.MG_USER_PREFIX; + List allRoles = + jooq.fetch( + "select distinct m.rolname as member, r.rolname as role" + + " from pg_catalog.pg_auth_members am " + + " join pg_catalog.pg_roles m on (m.oid = am.member)" + + "join pg_catalog.pg_roles r on (r.oid = am.roleid)" + + "where r.rolname LIKE {0} and m.rolname LIKE {1}", + roleFilter + "%", userFilter + "%"); + + for (Record userRecord : allRoles) { + String memberName = + userRecord.getValue("member", String.class).substring(userFilter.length()); + String roleName = userRecord.getValue("role", String.class).substring(roleFilter.length()); + members.add(new Member(memberName, roleName)); + } + return members; + } + + public void revokeRoles(String userName, List> members) { + try { + members.forEach( + member -> { + String prefixedRole = + Constants.MG_ROLE_PREFIX + member.get("schemaId") + "/" + member.get(ROLE); + jooq.execute( + "REVOKE {0} FROM {1}", + name(prefixedRole), name(Constants.MG_USER_PREFIX + userName)); + }); + } catch (DataAccessException dae) { + throw new SqlMolgenisException("Removal of role failed", dae); + } + } + + public void updateRoles(String userName, List> members) { + try { + members.forEach( + member -> { + String schemaId = member.get("schemaId"); + String role = member.get(ROLE); + String prefixedRole = MG_ROLE_PREFIX + schemaId + "/" + role; + String prefixedName = MG_USER_PREFIX + userName; + + List existingUserRoles = + this.getSchema(schemaId).getMembers().stream() + .filter(mem -> mem.getUser().equals(userName)) + .toList(); + if (existingUserRoles.iterator().hasNext()) { + existingUserRoles.forEach( + existingRole -> { + String oldRole = MG_ROLE_PREFIX + schemaId + "/" + existingRole.getRole(); + jooq.execute("REVOKE {0} FROM {1}", name(oldRole), name(prefixedName)); + }); + } + jooq.execute("GRANT {0} TO {1}", name(prefixedRole), name(prefixedName)); + }); + } catch (DataAccessException dae) { + throw new SqlMolgenisException("Updating of role failed", dae); + } + } } diff --git a/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/Database.java b/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/Database.java index 380cf673cf..9a87356956 100644 --- a/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/Database.java +++ b/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/Database.java @@ -101,4 +101,10 @@ public interface Database extends HasSettingsInterface { Map> getJavaScriptBindings(); List getLastUpdated(); + + List loadUserRoles(); + + void revokeRoles(String userName, List> revokedRoles); + + void updateRoles(String userName, List> roles); } diff --git a/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/User.java b/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/User.java index b3f04215ff..98d70d91f1 100644 --- a/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/User.java +++ b/backend/molgenis-emx2/src/main/java/org/molgenis/emx2/User.java @@ -2,10 +2,7 @@ import static java.util.Objects.requireNonNull; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; public class User extends HasSettings { private static final String TOKENS = "access-tokens";