Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions doc/release-notes/11919-list-assignable-roles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
There are two new API endpoints that list the roles a user can assign to others for a specific dataset or dataverse (`/api/datasets/$ID/assignments/userAssignableRoles` and `/api/dataverses/$ID/assignments/userAssignableRoles`).
They return only roles that the user is permitted to assign and that are relevant to the target dataset or dataverse.
Roles with higher permissions than the requesting user are excluded, and when querying for a dataset, roles that apply only at the dataverse level are filtered out.
42 changes: 42 additions & 0 deletions doc/sphinx-guides/source/api/native-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,27 @@ The fully expanded example above (without environment variables) looks like this

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/dataverses/root/assignments"

.. _list-assignable-roles-on-a-dataverse-api:

List Assignable Roles in a Dataverse Collection
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

List all the roles that are assignable by the current user at the given Dataverse collection:

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=root

curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/dataverses/$ID/assignments/userAssignableRoles"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/dataverses/root/assignments/userAssignableRoles"

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

Expand Down Expand Up @@ -2756,6 +2777,27 @@ The fully expanded example above (without environment variables) looks like this
.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/datasets/2347/assignments"

.. _list-assignable-roles-on-a-dataset-api:

List Assignable Roles in a Dataset
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

List all the roles that are assignable by the current user on the given dataset:

.. code-block:: bash

export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
export SERVER_URL=https://demo.dataverse.org
export ID=2347

curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/datasets/$ID/assignments/userAssignableRoles"

The fully expanded example above (without environment variables) looks like this:

.. code-block:: bash

curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/datasets/2347/assignments/userAssignableRoles"

.. _assign-role-on-a-dataset-api:

Expand Down
129 changes: 92 additions & 37 deletions src/main/java/edu/harvard/iq/dataverse/DataverseRoleServiceBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public class DataverseRoleServiceBean implements java.io.Serializable {
@EJB
SolrIndexServiceBean solrIndexService;
@EJB
PermissionServiceBean permissionService;
@EJB
IndexAsync indexAsync;

public DataverseRole save(DataverseRole aRole) {
Expand Down Expand Up @@ -87,11 +89,11 @@ public RoleAssignment save(RoleAssignment assignment, boolean createIndex, Datav
} else {
assignment = em.merge(assignment);
}

if (createIndex) {
indexAsync.indexRole(assignment);
}

// Check if ROLE_ASSIGNMENT_HISTORY feature flag is enabled
if (FeatureFlags.ROLE_ASSIGNMENT_HISTORY.enabled()) {
RoleAssignmentHistory entry = new RoleAssignmentHistory(assignment, req, RoleAssignmentHistory.ActionType.ASSIGN);
Expand All @@ -100,11 +102,11 @@ public RoleAssignment save(RoleAssignment assignment, boolean createIndex, Datav

return assignment;
}


/**
* Saves a RoleAssignmentHistory entry to the database.
*
*
* @param entry The RoleAssignmentHistory object to be saved
* @return The persisted RoleAssignmentHistory object
*/
Expand Down Expand Up @@ -181,42 +183,42 @@ public void revoke(RoleAssignment ra, DataverseRequest req) {
if (!em.contains(ra)) {
ra = em.merge(ra);
}

// Create history entry if feature flag is set
if (FeatureFlags.ROLE_ASSIGNMENT_HISTORY.enabled()) {
RoleAssignmentHistory entry = new RoleAssignmentHistory(ra, req, RoleAssignmentHistory.ActionType.REVOKE);
saveHistoryEntry(entry);
}

em.remove(ra);
/**
* @todo update permissionModificationTime here.
*/
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, DataverseRequest req) {
Set<DvObject> reindexSet = new HashSet<>();

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

// Create history entry if feature flag is set
if (FeatureFlags.ROLE_ASSIGNMENT_HISTORY.enabled()) {
RoleAssignmentHistory entry = new RoleAssignmentHistory(ra, req, RoleAssignmentHistory.ActionType.REVOKE);
saveHistoryEntry(entry);
}

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

indexAsync.indexRoles(reindexSet);
}

Expand Down Expand Up @@ -337,12 +339,10 @@ public List<RoleAssignment> directRoleAssignments(DvObject dvo) {
}

/**
* Get all the available roles in a given dataverse, mapped by the dataverse
* that defines them. Map entries are ordered by reversed hierarchy (root is
* always last).
* Get all the available roles in a given dataverse.
*
* @param dvId The id of dataverse whose available roles we query
* @return map of available roles.
* @return Set of available roles
*/
public Set<DataverseRole> availableRoles(Long dvId) {
Dataverse dv = em.find(Dataverse.class, dvId);
Expand All @@ -357,6 +357,61 @@ public Set<DataverseRole> availableRoles(Long dvId) {
return roles;
}

/**
* Get all the available roles for a given Dataset, DataFile or Dataverse.
* This excludes roles that are not relevant to the given DvObject type (e.g. for Datasets, this excludes roles that
* only have Dataverse-level permissions).
* Currently, the available roles for Datasets and DataFiles are gotten from the collection they are in.
*
* @param dvo The Dataset, DataFile or Dataverse whose available roles we query
* @return Set of available roles
*/
public Set<DataverseRole> availableRoles(DvObject dvo) {
Set<DataverseRole> roles = new HashSet<>();

// Get roles available for given DvObject
if (dvo instanceof Dataverse) {
roles = availableRoles(dvo.getId());

} else if (dvo instanceof Dataset) {
roles = availableRoles(dvo.getOwner().getId()).stream()
.filter(role -> role.permissions().stream()
.anyMatch(p -> p.appliesTo(Dataset.class)
|| p.appliesTo(DataFile.class)))
.collect(Collectors.toSet());

} else if (dvo instanceof DataFile) {
roles = availableRoles(dvo.getOwner().getOwner().getId()).stream()
.filter(role -> role.permissions().stream()
.anyMatch(p -> p.appliesTo(DataFile.class)))
.collect(Collectors.toSet());
}

return roles;
}

/**
* Get all the available roles for a given Dataset, DataFile or Dataverse that can be assigned by a given User.
* This excludes roles that are not relevant to the given DvObject type (e.g. for Datasets, this excludes roles that
* only have Dataverse-level permissions).
* Currently, the available roles for Datasets and DataFiles are gotten from the collection they are in.
*
* @param dvo The Dataset, DataFile or Dataverse whose available roles we query
* @param user The user whose available roles we query
* @return Set of available roles
*/
public Set<DataverseRole> availableRoles(DvObject dvo, User user) {
Set<DataverseRole> roles = availableRoles(dvo);

// Filter roles assignable by given user
Set<Permission> granted = permissionService.permissionsFor(user, dvo);
roles = roles.stream()
.filter(role -> granted.containsAll(role.permissions()))
.collect(Collectors.toSet());

return roles;
}

public List<DataverseRole> getDataverseRolesByPermission(Permission permissionIn, Long ownerId) {
/*
For a given permission and dataverse Id get all of the roles (built-in or owned by the dataverse)
Expand All @@ -372,38 +427,38 @@ For a given permission and dataverse Id get all of the roles (built-in or owned
}
return retVal;
}

/**
* Retrieves role assignment history for a specific definition point
*
*
* @param definitionPointId The ID of the definition point
* @return List of role assignment history entries
*/
public List<RoleAssignmentHistoryConsolidatedEntry> getRoleAssignmentHistory(Long definitionPointId) {
List<RoleAssignmentHistory> entries = em.createNamedQuery("RoleAssignmentHistory.findByDefinitionPointId", RoleAssignmentHistory.class)
.setParameter("definitionPointId", definitionPointId)
.getResultList();

return processRoleAssignmentEntries(entries, false);
}

/**
* Retrieves role assignment history for all files in a dataset
*
*
* @param datasetId The ID of the dataset
* @return List of role assignment history entries
*/
public List<RoleAssignmentHistoryConsolidatedEntry> getFilesRoleAssignmentHistory(Long datasetId) {
List<RoleAssignmentHistory> entries = em.createNamedQuery("RoleAssignmentHistory.findByOwnerId", RoleAssignmentHistory.class)
.setParameter("datasetId", datasetId)
.getResultList();

return processRoleAssignmentEntries(entries, true);
}

/**
* Common method to process role assignment history entries and create consolidated history entries
*
*
* @param entries List of role assignment history records
* @param combineEntries Whether to combine entries for different files
* @return List of role assignment history entries
Expand All @@ -430,7 +485,7 @@ private List<RoleAssignmentHistoryConsolidatedEntry> processRoleAssignmentEntrie
consolidatedEntry.setRevokedAt(entry.getActionTimestamp());
}
}

// Second pass: Combine entries with matching criteria if requested
if (combineEntries) {
Map<String, RoleAssignmentHistoryConsolidatedEntry> finalHistoryMap = new HashMap<>();
Expand Down Expand Up @@ -470,62 +525,62 @@ public static class RoleAssignmentHistoryConsolidatedEntry {
private String revokedBy;
private Date revokedAt;
private List<Long> definitionPointIds;

public RoleAssignmentHistoryConsolidatedEntry(String assigneeIdentifier, String roleName, Long definitionPointId) {
this.roleName = roleName;
this.assigneeIdentifier = assigneeIdentifier;
this.definitionPointIds = new ArrayList<Long>();
definitionPointIds.add(definitionPointId);
}

public void setRevokedAt(Date actionTimestamp) {
revokedAt = actionTimestamp;
}

public void setRevokedBy(String actionByIdentifier) {
revokedBy = actionByIdentifier;
}

public void setAssignedAt(Date actionTimestamp) {
assignedAt = actionTimestamp;
}

public void setAssignedBy(String actionByIdentifier) {
assignedBy = actionByIdentifier;
}

public String getRoleName() {
return roleName;
}

public String getAssigneeIdentifier() {
return assigneeIdentifier;
}

public String getAssignedBy() {
return assignedBy;
}

public Date getAssignedAt() {
return assignedAt;
}

public String getRevokedBy() {
return revokedBy;
}

public Date getRevokedAt() {
return revokedAt;
}

public List<Long> getDefinitionPointIds() {
return definitionPointIds;
}

public void addDefinitionPointId(Long definitionPointId) {
definitionPointIds.add(definitionPointId);
}

public String getDefinitionPointIdsAsString() {
return definitionPointIds.stream()
.map(Object::toString)
Expand Down
Loading