Skip to content

Commit

Permalink
feat(central): remove and enable/disable users via admin ui (#4167)
Browse files Browse the repository at this point in the history
* feat: remove and enable disable user
  • Loading branch information
MaxPostema authored Oct 9, 2024
1 parent dd6ebda commit a3de237
Show file tree
Hide file tree
Showing 10 changed files with 317 additions and 22 deletions.
115 changes: 113 additions & 2 deletions apps/central/src/components/admin/ManageUsers.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,47 @@
<ButtonAction @click="alterUser" class="mt-0">Update user</ButtonAction>
</form>
<h2>User list</h2>
<TableSimple class="bg-white" :rows="users" :columns="['email']" />
<TableSimple class="bg-white" :rows="users" :columns="['email', 'enabled']">
<template v-slot:rowheader="row">
<template
v-if="row.row.email !== 'admin' && row.row.email !== 'anonymous'"
>
<IconDanger
icon="trash"
@click="
userToDelete = row.row.email;
isModalShown = true;
"
:tooltip="`Delete ${row.row.email}`"
/>
<IconAction
v-if="row.row.enabled"
icon="user-check"
@click="disableUser(row.row.email)"
:tooltip="`disable ${row.row.email}`"
/>
<IconAction
v-else
icon="user-slash"
@click="enableUser(row.row.email)"
:tooltip="`re-enable ${row.row.email}`"
/>
</template>
</template>
</TableSimple>
<ConfirmModal
v-if="isModalShown"
title="Delete User"
:actionLabel="'Delete ' + userToDelete"
actionType="danger"
@close="isModalShown = false"
@confirmed="
removeUser(userToDelete);
userToDelete = '';
isModalShown = false;
"
/>

<Pagination
v-model="page"
:count="userCount"
Expand All @@ -39,6 +79,9 @@ import {
InputString,
InputPassword,
ButtonAction,
IconDanger,
IconAction,
ConfirmModal,
} from "molgenis-components";
export default {
Expand All @@ -51,6 +94,9 @@ export default {
InputString,
InputPassword,
ButtonAction,
IconDanger,
IconAction,
ConfirmModal,
},
props: {
session: {
Expand All @@ -72,6 +118,8 @@ export default {
alterSuccess: null,
alterLoading: false,
showSigninForm: true,
isModalShown: false,
userToDelete: "",
};
},
computed: {
Expand Down Expand Up @@ -111,7 +159,7 @@ export default {
this.loading = true;
request(
"graphql",
`{_admin{users(limit:${this.limit},offset:${this.offset}){email},userCount}}`
`{_admin{users(limit:${this.limit},offset:${this.offset}){email, enabled},userCount}}`
)
.then((data) => {
this.users = data._admin.users;
Expand All @@ -124,6 +172,69 @@ export default {
"internal error or permission denied. Did you log in as admin?";
});
},
removeUser(user) {
this.alterError = null;
this.alterLoading = true;
request(
"graphql",
`mutation{removeUser(email: "${user}"){status,message}}`
)
.then((data) => {
if (data.removeUser.status === "SUCCESS") {
this.alterSuccess = "Success. removed user: " + user;
this.getUserList();
} else {
this.alterError =
"Delete user failed: " + data.changePassword.message;
}
})
.catch((error) => {
this.alterError = "Delete user failed: " + error.response.message;
});
this.alterLoading = false;
},
enableUser(user) {
this.alterError = null;
this.alterLoading = true;
request(
"graphql",
`mutation{setEnabledUser(email: "${user}", enabled:true){status,message}}`
)
.then((data) => {
if (data.setEnabledUser.status === "SUCCESS") {
this.alterSuccess = "Success. enabled user: " + user;
this.getUserList();
} else {
this.alterError =
"Enable user failed: " + data.changePassword.message;
}
})
.catch((error) => {
this.alterError = "Enable user failed: " + error.response.message;
});
this.alterLoading = false;
},
disableUser(user) {
this.alterError = null;
this.alterLoading = true;
request(
"graphql",
`mutation{setEnabledUser(email: "${user}", enabled:false){status,message}}`
)
.then((data) => {
if (data.setEnabledUser.status === "SUCCESS") {
this.alterSuccess = "Success. disabled user: " + user;
this.getUserList();
} else {
this.alterError =
"Disable user failed: " + data.changePassword.message;
}
})
.catch((error) => {
this.alterError = "Disable user failed: " + error.response.message;
});
this.alterLoading = false;
},
},
watch: {
page() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

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;

Expand All @@ -27,6 +29,11 @@ private GraphlAdminFieldFactory() {
.name(EMAIL)
.type(Scalars.GraphQLString)
.build())
.field(
GraphQLFieldDefinition.newFieldDefinition()
.name(ENABLED)
.type(Scalars.GraphQLBoolean)
.build())
.field(
GraphQLFieldDefinition.newFieldDefinition()
.name(SETTINGS)
Expand Down Expand Up @@ -89,9 +96,41 @@ private static Object getUsers(SelectedField selectedField, Database db) {
}
}

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<String, Object> toGraphqlUser(User user) {
Map<String, Object> result = new LinkedHashMap<>();
result.put(EMAIL, user.getUsername());
result.put(ENABLED, user.getEnabled());
result.put(SETTINGS, mapSettingsToGraphql(user.getSettings()));
return result;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ 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));
}

// database operations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class GraphqlConstants {
public static final String FILTER_ARGUMENT = "filter";
public static final String KEY = "key";
public static final String PASSWORD = "password";
public static final String ENABLED = "enabled";
public static final String EMAIL = "email";
public static final String NAME = "name";
public static final String LOCALE = "locale";
Expand Down Expand Up @@ -51,6 +52,7 @@ public class GraphqlConstants {
public static final String TASK_DESCRIPTION = "description";
public static final String TASK_SUBTASKS = "subTasks";
public static final String TOKEN_NAME = "tokenName";
public static final String USER = "user";
public static final String USERS = "users";
public static final String ROLES = "roles";
public static final String MESSAGE = "message";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,16 +86,20 @@ public GraphQLFieldDefinition signinField(Database database) {
dataFetchingEnvironment -> {
String userName = dataFetchingEnvironment.getArgument(EMAIL);
String passWord = dataFetchingEnvironment.getArgument(PASSWORD);

if (database.hasUser(userName) && database.checkUserPassword(userName, passWord)) {
database.setActiveUser(userName);
GraphqlApiMutationResultWithToken result =
new GraphqlApiMutationResultWithToken(
GraphqlApiMutationResult.Status.SUCCESS,
JWTgenerator.createTemporaryToken(database, userName),
"Signed in as '%s'",
userName);
return result;
if (database.getUser(userName).getEnabled()) {
database.setActiveUser(userName);
GraphqlApiMutationResultWithToken result =
new GraphqlApiMutationResultWithToken(
GraphqlApiMutationResult.Status.SUCCESS,
JWTgenerator.createTemporaryToken(database, userName),
"Signed in as '%s'",
userName);
return result;
} else {
return new GraphqlApiMutationResult(
FAILED, "User '%s' disabled: check with your administrator", userName);
}
} else {
return new GraphqlApiMutationResult(
FAILED, "Sign in as '%s' failed: user or password unknown", userName);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class MetadataUtils {
private static final org.jooq.Table SCHEMA_METADATA = table(name(MOLGENIS, "schema_metadata"));
private static final org.jooq.Table TABLE_METADATA = table(name(MOLGENIS, "table_metadata"));
private static final org.jooq.Table COLUMN_METADATA = table(name(MOLGENIS, "column_metadata"));
private static final org.jooq.Table USERS_METADATA = table(name(MOLGENIS, "users_metadata"));
public static final org.jooq.Table USERS_METADATA = table(name(MOLGENIS, "users_metadata"));
private static final org.jooq.Table SETTINGS_METADATA =
table(name(MOLGENIS, "settings_metadata"));

Expand Down Expand Up @@ -98,8 +98,9 @@ public class MetadataUtils {
field(name("defaultValue"), VARCHAR.nullable(true));

// users
private static final Field<String> USER_NAME = field(name("username"), VARCHAR);
public static final Field<String> USER_NAME = field(name("username"), VARCHAR);
private static final Field<String> USER_PASS = field(name("password"), VARCHAR);
public static final Field<Boolean> USER_ENABLED = field(name("enabled"), BOOLEAN.nullable(false));

// settings field, reused by all other metadata
static final org.jooq.Field SETTINGS = field(name(org.molgenis.emx2.Constants.SETTINGS), JSON);
Expand Down Expand Up @@ -256,7 +257,9 @@ protected static void init(DSLContext j) {
}

t = jooq.createTableIfNotExists(USERS_METADATA);
t.columns(USER_NAME, USER_PASS).constraint(primaryKey(USER_NAME)).execute();
t.columns(USER_NAME, USER_PASS, USER_ENABLED)
.constraint(primaryKey(USER_NAME))
.execute();

t = jooq.createTableIfNotExists(SETTINGS_METADATA);
t.columns(TABLE_SCHEMA, SETTINGS_TABLE_NAME, SETTINGS_NAME, SETTINGS_VALUE)
Expand Down Expand Up @@ -389,13 +392,15 @@ protected static List<User> loadUsers(SqlDatabase db, int limit, int offset) {
List<User> users = new ArrayList<>();
for (org.jooq.Record user :
db.getJooq()
.select(USER_NAME, SETTINGS)
.select(USER_NAME, USER_ENABLED, SETTINGS)
.from(USERS_METADATA)
.orderBy(USER_NAME)
.limit(limit)
.offset(offset)
.fetchArray()) {
users.add(new User(db, user.get(USER_NAME), user.get(SETTINGS, Map.class)));
User newUser = new User(db, user.get(USER_NAME), user.get(SETTINGS, Map.class));
newUser.setEnabled(user.get(USER_ENABLED));
users.add(newUser);
}
return users;
} catch (Exception e) {
Expand Down Expand Up @@ -602,10 +607,12 @@ private static Column recordToColumn(org.jooq.Record col) {
}

public static void setUserPassword(DSLContext jooq, String user, String password) {
// TODO BEFORE MERGE: set USER_ACTIVE to current value and not to "TRUE"
jooq.insertInto(USERS_METADATA)
.columns(USER_NAME, USER_PASS)
.columns(USER_NAME, USER_ENABLED, USER_PASS)
.values(
field("{0}", String.class, user),
field("{0}", Boolean.class, Boolean.TRUE),
field("crypt({0}, gen_salt('bf'))", String.class, password))
.onConflict(USER_NAME)
.doUpdate()
Expand Down Expand Up @@ -663,8 +670,8 @@ public static Map<String, String> loadDatabaseSettings(DSLContext jooq) {
public static void saveUserMetadata(DSLContext jooq, User user) {
// don't update password via this route
jooq.insertInto(USERS_METADATA)
.columns(USER_NAME, SETTINGS)
.values(user.getUsername(), user.getSettings())
.columns(USER_NAME, USER_ENABLED, SETTINGS)
.values(user.getUsername(), true, user.getSettings())
.onConflict(USER_NAME)
.doUpdate()
.set(SETTINGS, user.getSettings())
Expand All @@ -676,6 +683,7 @@ public static User loadUserMetadata(SqlDatabase db, String userName) {
db.getJooq().selectFrom(USERS_METADATA).where(USER_NAME.eq(userName)).fetchOne();
if (userRecord != null) {
User result = new User(db, userName);
result.setEnabled(userRecord.get(USER_ENABLED));
result.setSettings(userRecord.get(SETTINGS, Map.class));
return result;
}
Expand Down
Loading

0 comments on commit a3de237

Please sign in to comment.