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

5307 dataset creator default role api #5324

Merged
merged 17 commits into from
Dec 5, 2018
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
21 changes: 21 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,34 @@ Create a New Role in a Dataverse
Creates a new role under dataverse ``id``. Needs a json file with the role description::

POST http://$SERVER/api/dataverses/$id/roles?key=$apiKey

Copy link
Member

Choose a reason for hiding this comment

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

I find these "POST" examples that require a file to be sent across the wire to be terribly confusing unless we provide a full curl example like the --upload-file facets.json example above. While were in here, can we change it to be a full curl example? Should we also add a note saying that we don't really recommend creating custom roles and that you should consider opening a GitHub issue or starting a conversation on the mailing list if you are feeling the need to create a custom role?

POSTed JSON example::

{
"alias": "sys1",
"name": “Restricted System Role”,
"description": “A person who may only add datasets.”,
"permissions": [
"AddDataset"
]
}

List Role Assignments in a Dataverse
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

List all the role assignments at the given dataverse::

GET http://$SERVER/api/dataverses/$id/assignments?key=$apiKey

Assign Default Role to User Creating a Dataset in a Dataverse
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Assign a default role to a user creating a dataset in a dataverse ``id`` where ``roleAlias`` is the database alias of the role to be assigned::

PUT http://$SERVER/api/dataverses/$id/defaultContributorRole/$roleAlias?key=$apiKey

Note: You may use "none" as the ``roleAlias``. This will prevent a user who creates a dataset from having any role on that dataset. It is not recommended for dataverses with human contributors.


Assign a New Role on a Dataverse
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down
3 changes: 3 additions & 0 deletions scripts/database/upgrades/upgrade_v4.9.4_to_v4.10.sql
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,6 @@ INSERT INTO setting(
VALUES (':UploadMethods', 'native/http');

ALTER TABLE actionlogrecord ALTER COLUMN info TYPE text;


ALTER TABLE dataverse ALTER COLUMN defaultcontributorrole_id DROP NOT NULL;
8 changes: 7 additions & 1 deletion src/main/java/Bundle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2035,6 +2035,8 @@ permission.roleWas=The role was {0}. To assign it to a user and/or group, click
permission.roleNotSaved=The role was not able to be saved.
permission.permissionsMissing=Permissions {0} missing.
permission.CannotAssigntDefaultPermissions=Cannot assign default permissions.
permission.default.contributor.role.none.decription=A person who has no permissions on a newly created dataset. Not recommended for dataverses with human contributors.
permission.default.contributor.role.none.name=None

#ManageFilePermissionsPage.java
permission.roleNotAbleToBeRemoved=The role assignment was not able to be removed.
Expand Down Expand Up @@ -2089,6 +2091,10 @@ datasets.api.updatePIDMetadata.auth.mustBeSuperUser=Forbidden. You must be a sup
datasets.api.updatePIDMetadata.success.for.single.dataset=Dataset {0} PID Metadata updated successfully.
datasets.api.updatePIDMetadata.success.for.update.all=All Dataset PID Metadata update completed successfully.

#Dataverses.java
dataverses.api.update.default.contributor.role.failure.role.not.found=Role {0} not found.
dataverses.api.update.default.contributor.role.success=Default contributor role for Dataverse {0} has been set to {1}.
dataverses.api.update.default.contributor.role.failure.role.does.not.have.dataset.permissions=Role {0} does not have dataset permissions.
#Access.java
access.api.allowRequests.failure.noDataset=Could not find Dataset with id: {0}
access.api.allowRequests.failure.noSave=Problem saving dataset {0}: {1}
Expand Down Expand Up @@ -2243,4 +2249,4 @@ rtabfileparser.ioexception.mismatch=Reading mismatch, line {0} of the Data file:
rtabfileparser.ioexception.boolean=Unexpected value for the Boolean variable ({0}):
rtabfileparser.ioexception.read=Couldn't read Boolean variable ({0})!
rtabfileparser.ioexception.parser1=R Tab File Parser: Could not obtain varQnty from the dataset metadata.
rtabfileparser.ioexception.parser2=R Tab File Parser: varQnty=0 in the dataset metadata!
rtabfileparser.ioexception.parser2=R Tab File Parser: varQnty=0 in the dataset metadata!
2 changes: 1 addition & 1 deletion src/main/java/edu/harvard/iq/dataverse/Dataverse.java
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ public String getIndexableCategoryName() {
private Set<DataverseRole> roles;

@ManyToOne
@JoinColumn(nullable = false)
@JoinColumn(nullable = true)
private DataverseRole defaultContributorRole;

public DataverseRole getDefaultContributorRole() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,13 @@ public DataverseRole findBuiltinRoleByAlias(String alias) {
.setParameter("alias", alias)
.getSingleResult();
}

public DataverseRole findCustomRoleByAliasAndOwner(String alias, Long ownerId) {
return em.createNamedQuery("DataverseRole.findCustomRoleByAliasAndOwner", DataverseRole.class)
.setParameter("alias", alias)
.setParameter("ownerId", ownerId)
.getSingleResult();
}

public void revoke(Set<DataverseRole> roles, RoleAssignee assignee, DvObject defPoint) {
for (DataverseRole role : roles) {
Expand Down
47 changes: 45 additions & 2 deletions src/main/java/edu/harvard/iq/dataverse/ManagePermissionsPage.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,49 @@ public void setAuthenticatedUsersContributorRoleAlias(String authenticatedUsersC
public String getDefaultContributorRoleAlias() {
return defaultContributorRoleAlias;
}

public Boolean isCustomDefaultContributorRole(){
if (defaultContributorRoleAlias == null){
initAccessSettings();
}
return !( defaultContributorRoleAlias.equals(DataverseRole.EDITOR) || defaultContributorRoleAlias.equals(DataverseRole.CURATOR));
}

public String getCustomDefaultContributorRoleName(){
if (dvObject instanceof Dataverse && isCustomDefaultContributorRole() ){
return defaultContributorRoleAlias.equals(DataverseRole.NONE) ? BundleUtil.getStringFromBundle("permission.default.contributor.role.none.name") : roleService.findCustomRoleByAliasAndOwner(defaultContributorRoleAlias,dvObject.getId()).getName();
} else {
return "";
}
}

public String getCustomDefaultContributorRoleAlias(){
if (dvObject instanceof Dataverse && isCustomDefaultContributorRole()){
return defaultContributorRoleAlias.equals(DataverseRole.NONE) ? DataverseRole.NONE : roleService.findCustomRoleByAliasAndOwner(defaultContributorRoleAlias,dvObject.getId()).getAlias();
} else {
return "";
}
}

public void setCustomDefaultContributorRoleAlias(String dummy){
//dummy method for interface
}

public void setCustomDefaultContributorRoleName(String dummy){
//dummy method for interface
}

public String getCustomDefaultContributorRoleDescription(){
if (dvObject instanceof Dataverse && isCustomDefaultContributorRole()){
return defaultContributorRoleAlias.equals(DataverseRole.NONE) ? BundleUtil.getStringFromBundle("permission.default.contributor.role.none.decription" ) :roleService.findCustomRoleByAliasAndOwner(defaultContributorRoleAlias,dvObject.getId() ).getDescription();
} else {
return "";
}
}

public void setCustomDefaultContributorRoleDescription(String dummy){
//dummy method for interface
}

public void setDefaultContributorRoleAlias(String defaultContributorRoleAlias) {
this.defaultContributorRoleAlias = defaultContributorRoleAlias;
Expand All @@ -265,8 +308,8 @@ public void initAccessSettings() {
break;
// @todo handle case where more than one role has been assigned to the AutenticatedUsers group!
}

defaultContributorRoleAlias = ((Dataverse) dvObject).getDefaultContributorRole().getAlias();
defaultContributorRoleAlias = ((Dataverse) dvObject).getDefaultContributorRole() == null ? DataverseRole.NONE : ((Dataverse) dvObject).getDefaultContributorRole().getAlias();
}
}

Expand Down
55 changes: 51 additions & 4 deletions src/main/java/edu/harvard/iq/dataverse/api/Dataverses.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DataverseFacet;
import edu.harvard.iq.dataverse.DataverseContact;
import edu.harvard.iq.dataverse.api.imports.ImportException;
import edu.harvard.iq.dataverse.api.imports.ImportServiceBean;
import edu.harvard.iq.dataverse.authorization.DataverseRole;
import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.GlobalId;
Expand All @@ -18,6 +16,8 @@
import edu.harvard.iq.dataverse.api.dto.ExplicitGroupDTO;
import edu.harvard.iq.dataverse.api.dto.RoleAssignmentDTO;
import edu.harvard.iq.dataverse.api.dto.RoleDTO;
import edu.harvard.iq.dataverse.api.imports.ImportException;
import edu.harvard.iq.dataverse.api.imports.ImportServiceBean;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.authorization.RoleAssignee;
import edu.harvard.iq.dataverse.authorization.groups.impl.explicit.ExplicitGroup;
Expand Down Expand Up @@ -52,6 +52,7 @@
import edu.harvard.iq.dataverse.engine.command.impl.RemoveRoleAssigneesFromExplicitGroupCommand;
import edu.harvard.iq.dataverse.engine.command.impl.RevokeRoleCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDataverseCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDataverseDefaultContributorRoleCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateDataverseMetadataBlocksCommand;
import edu.harvard.iq.dataverse.engine.command.impl.UpdateExplicitGroupCommand;
import edu.harvard.iq.dataverse.settings.SettingsServiceBean;
Expand Down Expand Up @@ -95,8 +96,10 @@
import javax.ws.rs.core.Response.Status;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.toJsonArray;
import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;
import java.util.Arrays;
import java.util.Date;
import java.util.Optional;
import javax.persistence.NoResultException;

/**
* A REST API for dataverses.
Expand All @@ -114,8 +117,6 @@ public class Dataverses extends AbstractApiBean {

@EJB
ImportServiceBean importService;
// @EJB
// SystemConfig systemConfig;

@POST
public Response addRoot(String body) {
Expand Down Expand Up @@ -816,6 +817,52 @@ public Response updateGroup(ExplicitGroupDTO groupDto,
new UpdateExplicitGroupCommand(req,
groupDto.apply(findExplicitGroupOrDie(findDataverseOrDie(dvIdtf), req, grpAliasInOwner)))))));
}

@PUT
@Path("{identifier}/defaultContributorRole/{roleAlias}")
public Response updateDefaultContributorRole(
@PathParam("identifier") String dvIdtf,
@PathParam("roleAlias") String roleAlias) {

DataverseRole defaultRole;

if (roleAlias.equals(DataverseRole.NONE)) {
defaultRole = null;
} else {
try {
Dataverse dv = findDataverseOrDie(dvIdtf);
defaultRole = rolesSvc.findCustomRoleByAliasAndOwner(roleAlias, dv.getId());
} catch (Exception nre) {
List<String> args = Arrays.asList(roleAlias);
String retStringError = BundleUtil.getStringFromBundle("dataverses.api.update.default.contributor.role.failure.role.not.found", args);
return error(Status.NOT_FOUND, retStringError);
}

if (!defaultRole.doesDvObjectClassHavePermissionForObject(Dataset.class)) {
List<String> args = Arrays.asList(roleAlias);
String retStringError = BundleUtil.getStringFromBundle("dataverses.api.update.default.contributor.role.failure.role.does.not.have.dataset.permissions", args);
return error(Status.BAD_REQUEST, retStringError);
}

}

try {
Dataverse dv = findDataverseOrDie(dvIdtf);

String defaultRoleName = defaultRole == null ? BundleUtil.getStringFromBundle("permission.default.contributor.role.none.name") : defaultRole.getName();

return response(req -> {
execCommand(new UpdateDataverseDefaultContributorRoleCommand(defaultRole, req, dv));
List<String> args = Arrays.asList(dv.getDisplayName(), defaultRoleName);
String retString = BundleUtil.getStringFromBundle("dataverses.api.update.default.contributor.role.success", args);
return ok(retString);
});

} catch (WrappedResponse wr) {
return wr.getResponse();
}

}

@DELETE
@Path("{identifier}/groups/{aliasInOwner}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
query= "SELECT r FROM DataverseRole r WHERE r.owner is null ORDER BY r.name"),
@NamedQuery(name = "DataverseRole.findBuiltinRoleByAlias",
query= "SELECT r FROM DataverseRole r WHERE r.alias=:alias AND r.owner is null"),
@NamedQuery(name = "DataverseRole.findCustomRoleByAliasAndOwner",
query= "SELECT r FROM DataverseRole r WHERE r.alias=:alias and (r.owner is null or r.owner.id=:ownerId)"),
@NamedQuery(name = "DataverseRole.listAll",
query= "SELECT r FROM DataverseRole r"),
@NamedQuery(name = "DataverseRole.deleteById",
Expand Down Expand Up @@ -66,6 +68,8 @@ public class DataverseRole implements Serializable {
public static final String CURATOR = "curator";
public static final String MEMBER = "member";

public static final String NONE = "none";


public static final Comparator<DataverseRole> CMP_BY_NAME = new Comparator<DataverseRole>(){

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,21 +75,23 @@ protected void handlePid(Dataset theDataset, CommandContext ctxt) throws Command
protected void postPersist( Dataset theDataset, CommandContext ctxt ){
// set the role to be default contributor role for its dataverse
String privateUrlToken = null;
RoleAssignment roleAssignment = new RoleAssignment(theDataset.getOwner().getDefaultContributorRole(),
getRequest().getUser(), theDataset, privateUrlToken);
ctxt.roles().save(roleAssignment, false);

// TODO: the above may be creating the role assignments and saving them
// in the database, but without properly linking them to the dataset
// (saveDataset, that the command returns). This may have been the reason
// for the github issue #4783 - where the users were losing their contributor
// permissions, when creating datasets AND uploading files in one step.
// In that scenario, an additional UpdateDatasetCommand is exectued on the
// dataset returned by the Create command. That issue was fixed by adding
// a full refresh of the datast with datasetService.find() between the
// two commands. But it may be a good idea to make sure they are properly
// linked here (?)
theDataset.setPermissionModificationTime(getTimestamp());
if (theDataset.getOwner().getDefaultContributorRole() != null) {
RoleAssignment roleAssignment = new RoleAssignment(theDataset.getOwner().getDefaultContributorRole(),
getRequest().getUser(), theDataset, privateUrlToken);
ctxt.roles().save(roleAssignment, false);

// TODO: the above may be creating the role assignments and saving them
// in the database, but without properly linking them to the dataset
// (saveDataset, that the command returns). This may have been the reason
// for the github issue #4783 - where the users were losing their contributor
// permissions, when creating datasets AND uploading files in one step.
// In that scenario, an additional UpdateDatasetCommand is exectued on the
// dataset returned by the Create command. That issue was fixed by adding
// a full refresh of the datast with datasetService.find() between the
// two commands. But it may be a good idea to make sure they are properly
// linked here (?)
theDataset.setPermissionModificationTime(getTimestamp());
}

if ( template != null ) {
ctxt.templates().incrementUsageCount(template.getId());
Expand Down
22 changes: 16 additions & 6 deletions src/main/webapp/permissions-configure.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,10 @@
#{bundle['dataverse.permissions.Q2']}
</label>
<div class="col-sm-offset-1">
<p:selectOneRadio id="defaultContributorRoleRadio" value="#{managePermissionsPage.defaultContributorRoleAlias}" layout="custom">
<f:selectItem itemLabel="#{bundle.editor}" itemValue="editor" />
<f:selectItem itemLabel="#{bundle.curator}" itemValue="curator" />
<p:selectOneRadio id="defaultContributorRoleRadio" value="#{managePermissionsPage.defaultContributorRoleAlias}" layout="custom" >
<f:selectItem itemLabel="#{bundle.editor}" itemValue="editor" />
<f:selectItem itemLabel="#{bundle.curator}" itemValue="curator" />
<f:selectItem itemLabel="#{managePermissionsPage.customDefaultContributorRoleName}" itemValue="#{managePermissionsPage.customDefaultContributorRoleAlias}" /> #{managePermissionsPage.customDefaultContributorRoleAlias}
</p:selectOneRadio>
<div class="radio no-padding-left">
<label for="opt1">
Expand All @@ -37,12 +38,21 @@
</label>
</div>
<div class="radio no-padding-left">
<label for="opt3">
<p:radioButton id="opt3" for="defaultContributorRoleRadio" itemIndex="1" />
<label for="opt2">
<p:radioButton id="opt2" for="defaultContributorRoleRadio" itemIndex="1" />
#{bundle.curator}
<span class="text-muted">#{bundle['dataverse.permissions.Q2.answer.curator.description']}</span>
</label>
</div>
</div>
<ui:fragment rendered="#{managePermissionsPage.isCustomDefaultContributorRole()}">
<div class="radio no-padding-left">
<label for="opt3">
<p:radioButton id="opt3" for="defaultContributorRoleRadio" itemIndex="2" />
#{managePermissionsPage.customDefaultContributorRoleName}
<span class="text-muted">- #{managePermissionsPage.customDefaultContributorRoleDescription}</span>
</label>
</div>
</ui:fragment>
</div>
</div>
</ui:composition>
3 changes: 3 additions & 0 deletions src/main/webapp/permissions-manage.xhtml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@
<ui:fragment rendered="#{managePermissionsPage.defaultContributorRoleAlias eq 'curator'}">
#{bundle.curator} <span class="text-muted">#{bundle['dataverse.permissions.Q2.answer.curator.description']}</span>
</ui:fragment>
<ui:fragment rendered="#{managePermissionsPage.isCustomDefaultContributorRole()}">
#{managePermissionsPage.customDefaultContributorRoleName} <span class="text-muted">#{managePermissionsPage.customDefaultContributorRoleDescription}</span>
</ui:fragment>
</p>
</div>
</div>
Expand Down