Skip to content

Commit 7877e90

Browse files
authored
Merge pull request #11560 from IQSS/11434-roles-api
New API Endpoint for retrieving user selectable roles
2 parents 19927e3 + ecf8820 commit 7877e90

File tree

8 files changed

+144
-8
lines changed

8 files changed

+144
-8
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
A new endpoint (`api/roles/userSelectable`) has been implemented, which returns the appropriate roles that the calling user can use as filters when searching within their data.

doc/sphinx-guides/source/api/native-api.rst

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5358,6 +5358,32 @@ The fully expanded example above (without environment variables) looks like this
53585358
53595359
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" -X DELETE "https://demo.dataverse.org/api/roles/:alias?alias=sys1"
53605360
5361+
Get User Selectable Roles
5362+
~~~~~~~~~~~~~~~~~~~~~~~~~
5363+
5364+
Returns the appropriate roles that the calling user can use as filters when searching within their data.
5365+
5366+
This endpoint returns the same set of roles displayed as filters on the MyData UI page. The logic for determining which roles are returned is as follows:
5367+
5368+
- If the user is a superuser, all available roles are returned.
5369+
5370+
- If the user is not a superuser, only their assigned roles are returned.
5371+
5372+
- If the user has no assigned roles, all roles are returned as a fallback.
5373+
5374+
.. code-block:: bash
5375+
5376+
export API_TOKEN=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
5377+
export SERVER_URL=https://demo.dataverse.org
5378+
5379+
curl -H "X-Dataverse-key:$API_TOKEN" "$SERVER_URL/api/roles/userSelectable"
5380+
5381+
The fully expanded example above (without environment variables) looks like this:
5382+
5383+
.. code-block:: bash
5384+
5385+
curl -H "X-Dataverse-key:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" "https://demo.dataverse.org/api/roles/userSelectable"
5386+
53615387
Explicit Groups
53625388
---------------
53635389

src/main/java/edu/harvard/iq/dataverse/RoleAssigneeServiceBean.java

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
1414
import edu.harvard.iq.dataverse.authorization.users.GuestUser;
1515
import edu.harvard.iq.dataverse.authorization.users.PrivateUrlUser;
16+
import edu.harvard.iq.dataverse.authorization.users.User;
1617
import edu.harvard.iq.dataverse.engine.command.DataverseRequest;
1718
import edu.harvard.iq.dataverse.mydata.MyDataFilterParams;
1819
import edu.harvard.iq.dataverse.privateurl.PrivateUrlUtil;
@@ -23,12 +24,13 @@
2324
import java.util.TreeMap;
2425
import java.util.logging.Logger;
2526
import java.util.stream.Collectors;
27+
28+
import edu.harvard.iq.dataverse.util.BundleUtil;
2629
import jakarta.annotation.PostConstruct;
2730
import jakarta.ejb.EJB;
2831
import jakarta.ejb.Stateless;
2932
import jakarta.inject.Named;
3033
import jakarta.persistence.EntityManager;
31-
import jakarta.persistence.NamedNativeQuery;
3234
import jakarta.persistence.PersistenceContext;
3335
import org.apache.commons.lang3.StringUtils;
3436

@@ -154,10 +156,41 @@ private String getRoleIdListClause(List<Long> roleIdList) {
154156
return " AND r.role_id IN (" + StringUtils.join(outputList, ",") + ")";
155157
}
156158

159+
/**
160+
* Retrieves the list of {@link DataverseRole}s selectable for the given {@link DataverseRequest}.
161+
* <p>
162+
* - If the user is a superuser, all roles are returned.<br>
163+
* - If the user is not a superuser, their assigned roles are returned. If none are assigned,
164+
* all roles are returned as a fallback.
165+
* <p>
166+
* This method is based on the logic from {@code MyDataPage.getRolesUsedToCreateCheckboxes}.
167+
* It has been implemented in this service to make the logic reusable from parts of the application
168+
* other than the JSF UI.
169+
*
170+
* @param request the dataverse request containing user context
171+
* @return a list of relevant {@link DataverseRole}s for the user
172+
* @throws NullPointerException if the request is null
173+
*/
174+
public List<DataverseRole> getSelectableDataverseRolesFor(DataverseRequest request) {
175+
if (request == null) {
176+
throw new NullPointerException(BundleUtil.getStringFromBundle("roleAssigneeServiceBean.error.dataverseRequestCannotBeNull"));
177+
}
178+
179+
User user = request.getUser();
180+
181+
if (user.isSuperuser()) {
182+
return dataverseRoleService.findAll();
183+
}
184+
185+
List<DataverseRole> assignedRoles = getAssigneeDataverseRoleFor(request);
186+
187+
return assignedRoles.isEmpty() ? dataverseRoleService.findAll() : assignedRoles;
188+
}
189+
157190
public List<DataverseRole> getAssigneeDataverseRoleFor(DataverseRequest dataverseRequest) {
158191

159192
if (dataverseRequest == null){
160-
throw new NullPointerException("dataverseRequest cannot be null!");
193+
throw new NullPointerException(BundleUtil.getStringFromBundle("roleAssigneeServiceBean.error.dataverseRequestCannotBeNull"));
161194
}
162195
AuthenticatedUser au = dataverseRequest.getAuthenticatedUser();
163196
if (au.getUserIdentifier() == null){
@@ -223,7 +256,7 @@ public List<Object[]> getAssigneeAndRoleIdListFor(MyDataFilterParams filterParam
223256

224257
public List<Long> getRoleIdListForGivenAssigneeDvObject(DataverseRequest dataverseRequest, List<Long> roleIdList, Long defPointId) {
225258
if (dataverseRequest == null){
226-
throw new NullPointerException("dataverseRequest cannot be null!");
259+
throw new NullPointerException(BundleUtil.getStringFromBundle("roleAssigneeServiceBean.error.dataverseRequestCannotBeNull"));
227260
}
228261
AuthenticatedUser au = dataverseRequest.getAuthenticatedUser();
229262
if (au.getUserIdentifier() == null){
@@ -292,7 +325,7 @@ private String getGroupIdentifierClause(String roleAssigneeIdentifier, List<Stri
292325

293326
public List<Object[]> getRoleIdsFor(DataverseRequest dataverseRequest, List<Long> dvObjectIdList) {
294327
if (dataverseRequest == null){
295-
throw new NullPointerException("dataverseRequest cannot be null!");
328+
throw new NullPointerException(BundleUtil.getStringFromBundle("roleAssigneeServiceBean.error.dataverseRequestCannotBeNull"));
296329
}
297330
AuthenticatedUser au = dataverseRequest.getAuthenticatedUser();
298331
if (au.getUserIdentifier() == null){

src/main/java/edu/harvard/iq/dataverse/api/Roles.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,11 @@ public Response createNewRole(@Context ContainerRequestContext crc,
6666
new CreateRoleCommand(roleDto.asRole(),
6767
req,findDataverseOrDie(dvoIdtf))))), getRequestUser(crc));
6868
}
69-
69+
70+
@GET
71+
@AuthRequired
72+
@Path("userSelectable")
73+
public Response getUserSelectableRoles(@Context ContainerRequestContext crc) {
74+
return response(req -> ok(jsonDataverseRoles(roleAssigneeSvc.getSelectableDataverseRolesFor(req))), getRequestUser(crc));
75+
}
7076
}

src/main/java/edu/harvard/iq/dataverse/util/json/JsonPrinter.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ public static <E extends Enum> JsonArrayBuilder enumsToJson(Collection<E> collec
221221
return arr;
222222
}
223223

224+
public static JsonArrayBuilder jsonDataverseRoles(List<DataverseRole> roles) {
225+
JsonArrayBuilder jsonArrayOfDataverseRoles = Json.createArrayBuilder();
226+
for (DataverseRole role : roles) {
227+
jsonArrayOfDataverseRoles.add(json(role));
228+
}
229+
return jsonArrayOfDataverseRoles;
230+
}
231+
224232
public static JsonObjectBuilder json(DataverseRole role) {
225233
JsonObjectBuilder bld = jsonObjectBuilder()
226234
.add("alias", role.getAlias())

src/main/java/propertyFiles/Bundle.properties

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3206,3 +3206,6 @@ updateDatasetFieldsCommand.api.processDatasetUpdate.parseError=Error parsing dat
32063206

32073207
#AbstractApiBean.java
32083208
abstractApiBean.error.datasetInternalVersionNumberIsOutdated=Dataset internal version number {0} is outdated
3209+
3210+
#RoleAssigneeServiceBean.java
3211+
roleAssigneeServiceBean.error.dataverseRequestCannotBeNull=DataverseRequest cannot be null.

src/test/java/edu/harvard/iq/dataverse/api/RolesIT.java

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11

22
package edu.harvard.iq.dataverse.api;
33

4+
import edu.harvard.iq.dataverse.authorization.DataverseRole;
45
import io.restassured.RestAssured;
56
import io.restassured.path.json.JsonPath;
67
import io.restassured.response.Response;
7-
import static jakarta.ws.rs.core.Response.Status.FORBIDDEN;
8+
89
import java.util.logging.Logger;
10+
11+
import static jakarta.ws.rs.core.Response.Status.*;
912
import static org.hamcrest.CoreMatchers.equalTo;
1013
import static org.junit.jupiter.api.Assertions.assertEquals;
1114

12-
import org.junit.jupiter.api.Assertions;
1315
import org.junit.jupiter.api.BeforeAll;
1416
import org.junit.jupiter.api.Test;
1517

@@ -108,5 +110,55 @@ public void testCreateDeleteRoles() {
108110
assertEquals("OK", status);
109111

110112
}
111-
113+
114+
@Test
115+
public void testGetUserSelectableRoles() {
116+
Response createAdminUser = UtilIT.createRandomUser();
117+
118+
String adminUsername = UtilIT.getUsernameFromResponse(createAdminUser);
119+
String adminApiToken = UtilIT.getApiTokenFromResponse(createAdminUser);
120+
UtilIT.makeSuperUser(adminUsername);
121+
122+
Response createUser = UtilIT.createRandomUser();
123+
124+
String username = UtilIT.getUsernameFromResponse(createUser);
125+
String apiToken = UtilIT.getApiTokenFromResponse(createUser);
126+
127+
// Non-superuser with no assigned roles: return all roles as fallback.
128+
129+
Response getUserSelectableRolesResponse = UtilIT.getUserSelectableRoles(apiToken);
130+
131+
getUserSelectableRolesResponse.then().assertThat()
132+
.statusCode(OK.getStatusCode())
133+
.body("data.size()", equalTo(8));
134+
135+
// Non-superuser with assigned role: return assigned role.
136+
137+
Response createDataverseResponse = UtilIT.createRandomDataverse(adminApiToken);
138+
createDataverseResponse.then().assertThat()
139+
.statusCode(CREATED.getStatusCode());
140+
141+
String dataverseAlias = UtilIT.getAliasFromResponse(createDataverseResponse);
142+
143+
Response grantUserAddDataset = UtilIT.grantRoleOnDataverse(dataverseAlias, DataverseRole.DS_CONTRIBUTOR, "@" + username, adminApiToken);
144+
145+
grantUserAddDataset.then().assertThat()
146+
.statusCode(OK.getStatusCode())
147+
.body("data.assignee", equalTo("@" + username))
148+
.body("data._roleAlias", equalTo("dsContributor"));
149+
150+
getUserSelectableRolesResponse = UtilIT.getUserSelectableRoles(apiToken);
151+
getUserSelectableRolesResponse.then().assertThat()
152+
.statusCode(OK.getStatusCode())
153+
.body("data.size()", equalTo(1))
154+
.body("data[0].alias", equalTo(DataverseRole.DS_CONTRIBUTOR));
155+
156+
// Superuser: return all roles.
157+
158+
getUserSelectableRolesResponse = UtilIT.getUserSelectableRoles(adminApiToken);
159+
160+
getUserSelectableRolesResponse.then().assertThat()
161+
.statusCode(OK.getStatusCode())
162+
.body("data.size()", equalTo(8));
163+
}
112164
}

src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4815,4 +4815,11 @@ public static Response updateDatasetFilesMetadata(String datasetIdOrPersistentId
48154815
return given().header(API_TOKEN_HTTP_HEADER, apiToken).contentType(ContentType.JSON).body(jsonArray.toString())
48164816
.post("/api/datasets/" + idInPath + "/files/metadata" + optionalQueryParam);
48174817
}
4818+
4819+
static Response getUserSelectableRoles(String apiToken) {
4820+
return given()
4821+
.header(API_TOKEN_HTTP_HEADER, apiToken)
4822+
.contentType("application/json")
4823+
.get("/api/roles/userSelectable");
4824+
}
48184825
}

0 commit comments

Comments
 (0)