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

4055 remove user roles #4138

Merged
merged 10 commits into from
Sep 25, 2017
Merged
11 changes: 6 additions & 5 deletions doc/sphinx-guides/source/admin/user-administration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ This section focuses on user administration tools and tasks.
.. contents:: Contents:
:local:

Manage Users Table
------------------
Manage Users
------------

The Manage Users table gives the network administrator a list of all user accounts in table form. It lists username, full name, email address, and whether or not the user has Superuser status.
The Manage Users table gives the network administrator a list of all user accounts in table form. You can access it by clicking the "Manage Users" button on the Dashboard, which is linked from the header of all Dataverse pages (if you're logged in as an administrator). It lists username, full name, email address, affiliation, the authentication method they use, the roles their account has been granted, and whether or not they have Superuser status.

Usernames are listed alphabetically and clicking on a username takes you to the account page that contains detailed information on that account.
Users are listed alphabetically by username. The search bar above the table allows you to search for a specific user. It performs a right-truncated wildcard search of the Username, Name, and Email columns. This means, if you search "baseba" then it will search those three columns for any string of text that begins with "baseba", e.g. "baseball" or "baseballfan".

If you would like to remove all roles/permissions from a user's account (in the event of their leaving your organization, for example) then you can do so by clicking the "Remove All" button under the Roles column. This will keep the user's account active, but will revert it to put the account on the level of a default user with default permissions.

You can access the Manage Users table by clicking the "Manage Users" button on the Dashboard, which is linked from the header of all Dataverse pages (if you're loggied in as an administrator).

Confirm Email
-------------
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,12 @@ dashboard.list_users.tbl_header.authProviderFactoryAlias=Authentication
dashboard.list_users.tbl_header.createdTime=Created Time
dashboard.list_users.tbl_header.lastLoginTime=Last Login Time
dashboard.list_users.tbl_header.lastApiUseTime=Last API Use Time
dashboard.list_users.tbl_header.roles.removeAll=Remove All
dashboard.list_users.tbl_header.roles.removeAll.header=Remove All Roles
dashboard.list_users.tbl_header.roles.removeAll.confirmationText=Are you sure you want to remove all roles for user {0}?
dashboard.list_users.removeAll.message.success=All roles have been removed for user {0}.
dashboard.list_users.removeAll.message.failure=Failed to remove roles for user {0}.

dashboard.list_users.toggleSuperuser=Edit Superuser Status
dashboard.list_users.toggleSuperuser.confirmationText.add=Are you sure you want to enable superuser status for user {0}?
dashboard.list_users.toggleSuperuser.confirmationText.remove=Are you sure you want to disable superuser status for user {0}?
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import edu.harvard.iq.dataverse.authorization.RoleAssignee;
import edu.harvard.iq.dataverse.authorization.users.User;
import edu.harvard.iq.dataverse.authorization.RoleAssignmentSet;
import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroup;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.search.IndexAsync;
import edu.harvard.iq.dataverse.search.IndexResponse;
import edu.harvard.iq.dataverse.search.IndexServiceBean;
Expand Down Expand Up @@ -126,7 +128,7 @@ public void revoke( Set<DataverseRole> roles, RoleAssignee assignee, DvObject de
}
em.refresh(assignee);
}
public void revoke( RoleAssignment ra ) {
if ( ! em.contains(ra) ) {
ra = em.merge(ra);
Expand All @@ -138,6 +140,27 @@ public void revoke( RoleAssignment ra ) {
indexAsync.indexRole(ra);
}

// "nuclear" remove-all roles for a user or group:
// (Note that all the "definition points" - i.e., the dvObjects
// on which the roles were assigned - need to be reindexed for permissions
// once the role assignments are removed!
public void revokeAll(RoleAssignee assignee) {
Set<DvObject> reindexSet = new HashSet<>();

for (RoleAssignment ra : roleAssigneeService.getAssignmentsFor(assignee.getIdentifier())) {
if ( ! em.contains(ra) ) {
ra = em.merge(ra);
}
em.remove(ra);

reindexSet.add(ra.getDefinitionPoint());
}

indexAsync.indexRoles(reindexSet);
}



public RoleAssignmentSet roleAssignments( User user, Dataverse dv ) {
RoleAssignmentSet retVal = new RoleAssignmentSet(user);
while ( dv != null ) {
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/RoleAssignment.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@
@NamedQuery( name = "RoleAssignment.listByPrivateUrlToken",
query = "SELECT r FROM RoleAssignment r WHERE r.privateUrlToken=:privateUrlToken" ),
@NamedQuery( name = "RoleAssignment.deleteByAssigneeIdentifier_RoleIdDefinition_PointId",
query = "DELETE FROM RoleAssignment r WHERE r.assigneeIdentifier=:userId AND r.role.id=:roleId AND r.definitionPoint.id=:definitionPointId"),
query = "DELETE FROM RoleAssignment r WHERE r.assigneeIdentifier=:assigneeIdentifier AND r.role.id=:roleId AND r.definitionPoint.id=:definitionPointId"),
@NamedQuery( name = "RoleAssignment.deleteAllByAssigneeIdentifier",
query = "DELETE FROM RoleAssignment r WHERE r.assigneeIdentifier=:assigneeIdentifier")
})
public class RoleAssignment implements java.io.Serializable {
@Id
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -243,4 +243,19 @@ protected Set<ExplicitGroup> findClosure( Set<ExplicitGroup> seed ) {
return result;
}

/**
*
* Fully strips the assignee of membership in all the explicit groups.
*
* @param assignee User or Group
*/
public void revokeAllGroupsForAssignee(RoleAssignee assignee) {
if (assignee instanceof AuthenticatedUser) {
em.createNativeQuery("DELETE FROM explicitgroup_authenticateduser WHERE containedauthenticatedusers_id=" + ((AuthenticatedUser) assignee).getId()).executeUpdate();
} else if (assignee instanceof ExplicitGroup) {
em.createNativeQuery("DELETE FROM explicitgroup_explicitgroup WHERE containedexplicitgroups_id=" + ((ExplicitGroup) assignee).getId()).executeUpdate();
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,15 @@
import edu.harvard.iq.dataverse.authorization.AuthenticationServiceBean;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.engine.command.impl.GrantSuperuserStatusCommand;
import edu.harvard.iq.dataverse.engine.command.impl.RevokeAllRolesCommand;
import edu.harvard.iq.dataverse.engine.command.impl.RevokeSuperuserStatusCommand;
import edu.harvard.iq.dataverse.mydata.Pager;
import edu.harvard.iq.dataverse.userdata.UserListMaker;
import edu.harvard.iq.dataverse.userdata.UserListResult;
import edu.harvard.iq.dataverse.util.BundleUtil;
import edu.harvard.iq.dataverse.util.JsfHelper;
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -216,6 +220,31 @@ public void cancelSuperuserStatusChange(){
selectedUserPersistent = null;
}

// Methods for the removeAllRoles for a user :

public void removeUserRoles() {
logger.fine("Get persisent AuthenticatedUser for id: " + selectedUserDetached.getId());
selectedUserPersistent = userService.find(selectedUserDetached.getId());

selectedUserDetached.setRoles(null); // for display
try {
commandEngine.submit(new RevokeAllRolesCommand(selectedUserPersistent, dvRequestService.getDataverseRequest()));
} catch (Exception ex) {
// error message to show on the page:
JsfHelper.addErrorMessage(BundleUtil.getStringFromBundle("dashboard.list_users.removeAll.message.failure", Arrays.asList(selectedUserPersistent.getUserIdentifier())));
return;
}
// success message:
JsfHelper.addSuccessMessage(BundleUtil.getStringFromBundle("dashboard.list_users.removeAll.message.success", Arrays.asList(selectedUserPersistent.getUserIdentifier())));
}

public String getConfirmRemoveRolesMessage() {
if (selectedUserDetached != null) {
return BundleUtil.getStringFromBundle("dashboard.list_users.tbl_header.roles.removeAll.confirmationText", Arrays.asList(selectedUserDetached.getUserIdentifier()));
}
return BundleUtil.getStringFromBundle("dashboard.list_users.tbl_header.roles.removeAll.confirmationText");
}

public String getAuthProviderFriendlyName(String authProviderId){

return AuthenticationProvider.getFriendlyName(authProviderId);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package edu.harvard.iq.dataverse.engine.command.impl;

import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.authorization.RoleAssignee;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.engine.command.AbstractVoidCommand;
import edu.harvard.iq.dataverse.engine.command.CommandContext;
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
import edu.harvard.iq.dataverse.engine.command.RequiredPermissions;
import edu.harvard.iq.dataverse.engine.command.exception.CommandException;
import edu.harvard.iq.dataverse.engine.command.exception.PermissionException;

/**
*
* Revokes all roles for a assignee.
* (Note that in addition to deleting the explicit role assignments,
* it also strips the assignee of membership in any groups!)
* @author Leonid Andreev
*/
// the permission annotation is open, since this is a superuser-only command -
// and that's enforced in the command body:
@RequiredPermissions({})
public class RevokeAllRolesCommand extends AbstractVoidCommand {

private final RoleAssignee assignee;

public RevokeAllRolesCommand(RoleAssignee assignee, DataverseRequest aRequest) {
super(aRequest, (Dataverse)null);
this.assignee = assignee;
}

@Override
protected void executeImpl(CommandContext ctxt) throws CommandException {
if (!(getUser() instanceof AuthenticatedUser) || !getUser().isSuperuser()) {
throw new PermissionException("Revoke Superuser status command can only be called by superusers.",
this, null, null);
}

try {
ctxt.roles().revokeAll(assignee);

ctxt.explicitGroups().revokeAllGroupsForAssignee(assignee);

} catch (Exception ex) {
throw new CommandException("Failed to revoke role assignments and/or group membership", this);
}
}

}
10 changes: 10 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/search/IndexAsync.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package edu.harvard.iq.dataverse.search;

import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.RoleAssignment;
import java.util.Collection;
import java.util.logging.Logger;
import javax.ejb.Asynchronous;
import javax.ejb.EJB;
Expand All @@ -19,5 +21,13 @@ public void indexRole(RoleAssignment roleAssignment) {
IndexResponse indexResponse = solrIndexService.indexPermissionsOnSelfAndChildren(roleAssignment.getDefinitionPoint());
logger.fine("output from indexing operations: " + indexResponse);
}

@Asynchronous
public void indexRoles(Collection<DvObject> dvObjects) {
for (DvObject dvObject : dvObjects) {
IndexResponse indexResponse = solrIndexService.indexPermissionsOnSelfAndChildren(dvObject);
logger.fine("output from permission indexing operations (dvobject " + dvObject.getId() + ": " + indexResponse);
}
}

}
24 changes: 21 additions & 3 deletions src/main/webapp/dashboard-users.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,23 @@
<p:column headerText="#{bundle['dashboard.list_users.tbl_header.affiliation']}">
<h:outputText value="#{user.affiliation}" />
</p:column>
<p:column headerText="#{bundle['dashboard.list_users.tbl_header.roles']}">
<h:outputText value="#{user.roles}" />
</p:column>
<p:column width="12%" headerText="#{bundle['dashboard.list_users.tbl_header.authProviderFactoryAlias']}">
<h:outputText value="#{DashboardUsersPage.getAuthProviderFriendlyName(user.authProviderId)}" />
</p:column>
<p:column headerText="#{bundle['dashboard.list_users.tbl_header.roles']}">
<h:outputText value="#{user.roles}" />
<p:commandButton id="removeRolesButton"
rendered="#{!empty user.roles}"
value="#{bundle['dashboard.list_users.tbl_header.roles.removeAll']}"
styleClass="btn btn-default"
type="button">
<p:ajax event="click"
oncomplete="PF('removeRolesConfirm').show();"
process="@this"
listener="#{DashboardUsersPage.setUserToToggleSuperuserStatus(user)}"
update=":dashboardUsersForm:removeRolesConfirm"/>
</p:commandButton>
</p:column>
<p:column width="9%" class="text-center" headerText="#{bundle['dashboard.list_users.tbl_header.isSuperuser']}">
<!-- A simple implementation of the superuser status toggle - via a boolean checkbox with an immediate ajax update. -->
<!-- Uses our standard approach, of showing a confirmation popup ("are you sure you want to toggle this? ...") first, -->
Expand All @@ -92,6 +103,13 @@
<p:commandButton styleClass="btn btn-default" value="#{bundle['cancel']}" action="#{DashboardUsersPage.cancelSuperuserStatusChange()}" update=":dashboardUsersForm:userTable,:messagePanel" onclick="PF('toggleSuperuserConfirmation').hide();"/>
</div>
</p:dialog>
<p:dialog id="removeRolesConfirm" header="#{bundle['dashboard.list_users.tbl_header.roles.removeAll.header']}" widgetVar="removeRolesConfirm">
<p class="text-danger"><span class="glyphicon glyphicon-warning-sign"/> #{DashboardUsersPage.confirmRemoveRolesMessage}</p>
<div class="button-block">
<p:commandButton styleClass="btn btn-default" value="#{bundle.yes}" update="userTable,:messagePanel" oncomplete="PF('removeRolesConfirm').hide();" action="#{DashboardUsersPage.removeUserRoles()}" />
<p:commandButton styleClass="btn btn-default" value="#{bundle.no}" onclick="PF('removeRolesConfirm').hide();" type="button" />
</div>
</p:dialog>
</h:form>
</ui:define>
</ui:composition>
Expand Down