Skip to content

Commit

Permalink
added api for optimized lookup for a user
Browse files Browse the repository at this point in the history
  • Loading branch information
stevenwinship committed Oct 3, 2024
1 parent a0cb73d commit cfd9da8
Show file tree
Hide file tree
Showing 5 changed files with 219 additions and 1 deletion.
49 changes: 49 additions & 0 deletions src/main/java/edu/harvard/iq/dataverse/PermissionServiceBean.java
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,45 @@ public class PermissionServiceBean {
@Inject
DatasetVersionFilesServiceBean datasetVersionFilesServiceBean;

private static final String LIST_ALL_DATAVERSES_USER_HAS_PERMISSION = "WITH grouplist AS (\n" +
" SELECT explicitgroup_authenticateduser.explicitgroup_id as id FROM explicitgroup_authenticateduser\n" +
" WHERE explicitgroup_authenticateduser.containedauthenticatedusers_id = @USERID\n" +
")\n" +
"\n" +
"SELECT * FROM DATAVERSE WHERE id IN (\n" +
" SELECT definitionpoint_id \n" +
" FROM roleassignment\n" +
" WHERE roleassignment.assigneeidentifier IN (\n" +
" SELECT CONCAT('&explicit/', explicitgroup.groupalias) as assignee\n" +
" FROM explicitgroup\n" +
" WHERE explicitgroup.id IN (\n" +
" (\n" +
" SELECT explicitgroup.id id\n" +
" FROM explicitgroup \n" +
" WHERE explicitgroup.id IN (SELECT id FROM grouplist)\n" +
" ) UNION (\n" +
" SELECT explicitgroup_explicitgroup.containedexplicitgroups_id id\n" +
" FROM explicitgroup_explicitgroup\n" +
" WHERE explicitgroup_explicitgroup.explicitgroup_id IN (SELECT id FROM grouplist)\n" +
" AND \n" +
" (SELECT count(*)\n" +
" FROM dataverserole\n" +
" WHERE dataverserole.id = roleassignment.role_id and (dataverserole.permissionbits & @PERMISSIONBIT !=0)) > 0\n" +
" )\n" +
" )\n" +
" ) UNION (\n" +
" SELECT definitionpoint_id \n" +
" FROM roleassignment\n" +
" WHERE roleassignment.assigneeidentifier = (\n" +
" SELECT CONCAT('@', authenticateduser.useridentifier)\n" +
" FROM authenticateduser \n" +
" WHERE authenticateduser.id = @USERID)\n" +
" AND \n" +
" (SELECT count(*)\n" +
" FROM dataverserole\n" +
" WHERE dataverserole.id = roleassignment.role_id and (dataverserole.permissionbits & @PERMISSIONBIT !=0)) > 0\n" +
" )\n" +
")";
/**
* A request-level permission query (e.g includes IP ras).
*/
Expand Down Expand Up @@ -888,4 +927,14 @@ private boolean hasUnrestrictedReleasedFiles(DatasetVersion targetDatasetVersion
Long result = em.createQuery(criteriaQuery).getSingleResult();
return result > 0;
}

public List<Dataverse> findPermittedCollections(AuthenticatedUser user, int permissionBit) {
if (user != null) {
String sqlCode = LIST_ALL_DATAVERSES_USER_HAS_PERMISSION
.replace("@USERID", String.valueOf(user.getId()))
.replace("@PERMISSIONBIT", String.valueOf(permissionBit));
return em.createNativeQuery(sqlCode, Dataverse.class).getResultList();
}
return null;
}
}
24 changes: 23 additions & 1 deletion src/main/java/edu/harvard/iq/dataverse/api/Users.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.authorization.users.User;
import edu.harvard.iq.dataverse.engine.command.impl.ChangeUserIdentifierCommand;
import edu.harvard.iq.dataverse.engine.command.impl.GetUserPermittedCollectionsCommand;
import edu.harvard.iq.dataverse.engine.command.impl.GetUserTracesCommand;
import edu.harvard.iq.dataverse.engine.command.impl.MergeInAccountCommand;
import edu.harvard.iq.dataverse.engine.command.impl.RevokeAllRolesCommand;
Expand Down Expand Up @@ -260,5 +261,26 @@ public Response getTracesElement(@Context ContainerRequestContext crc, @Context
return ex.getResponse();
}
}

@GET
@AuthRequired
@Path("{identifier}/allowedcollections/{permission}")
@Produces("text/csv, application/json")
public Response getUserPermittedCollections(@Context ContainerRequestContext crc, @Context Request req, @PathParam("identifier") String identifier, @PathParam("permission") String permission) {
AuthenticatedUser authenticatedUser = null;
try {
authenticatedUser = getRequestAuthenticatedUserOrDie(crc);
if (!authenticatedUser.isSuperuser()) {
return error(Response.Status.FORBIDDEN, "This API call can be used by superusers only");
}
} catch (WrappedResponse ex) {
return error(Response.Status.UNAUTHORIZED, "Authentication is required.");
}
try {
AuthenticatedUser userToQuery = authSvc.getAuthenticatedUser(identifier);
JsonObjectBuilder jsonObj = execCommand(new GetUserPermittedCollectionsCommand(createDataverseRequest(getRequestUser(crc)), userToQuery, permission));
return ok(jsonObj);
} catch (WrappedResponse ex) {
return ex.getResponse();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package edu.harvard.iq.dataverse.engine.command.impl;

import edu.harvard.iq.dataverse.Dataverse;
import edu.harvard.iq.dataverse.DvObject;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.engine.command.AbstractCommand;
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 jakarta.json.Json;
import jakarta.json.JsonArrayBuilder;
import jakarta.json.JsonObjectBuilder;

import java.util.List;
import java.util.logging.Logger;

import static edu.harvard.iq.dataverse.util.json.JsonPrinter.json;

@RequiredPermissions({})
public class GetUserPermittedCollectionsCommand extends AbstractCommand<JsonObjectBuilder> {
private static final Logger logger = Logger.getLogger(GetUserPermittedCollectionsCommand.class.getCanonicalName());

private DataverseRequest request;
private AuthenticatedUser user;
private String permission;
public GetUserPermittedCollectionsCommand(DataverseRequest request, AuthenticatedUser user, String permission) {
super(request, (DvObject) null);
this.request = request;
this.user = user;
this.permission = permission;
}

@Override
public JsonObjectBuilder execute(CommandContext ctxt) throws CommandException {
if (user == null) {
throw new CommandException("User not found.", this);
}
int permissionBit;
try {
permissionBit = permission.equalsIgnoreCase("any") ?
Integer.MAX_VALUE : (1 << Permission.valueOf(permission).ordinal());
} catch (IllegalArgumentException e) {
throw new CommandException("Permission not valid.", this);
}
List<Dataverse> collections = ctxt.permissions().findPermittedCollections(user, permissionBit);
if (collections != null) {
JsonObjectBuilder job = Json.createObjectBuilder();
JsonArrayBuilder jab = Json.createArrayBuilder();
for (Dataverse dv : collections) {
jab.add(json(dv));
}
job.add("count", collections.size());
job.add("items", jab);
return job;
}
return null;
}
}
78 changes: 78 additions & 0 deletions src/test/java/edu/harvard/iq/dataverse/api/UsersIT.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package edu.harvard.iq.dataverse.api;

import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.util.BundleUtil;
import io.restassured.RestAssured;
import static io.restassured.RestAssured.given;
import io.restassured.http.ContentType;
Expand Down Expand Up @@ -516,6 +518,82 @@ public void testDeleteAuthenticatedUser() {

}

@Test
public void testUserPermittedDataverses() {
Response createSuperuser = UtilIT.createRandomUser();
String superuserUsername = UtilIT.getUsernameFromResponse(createSuperuser);
String superuserApiToken = UtilIT.getApiTokenFromResponse(createSuperuser);
Response toggleSuperuser = UtilIT.makeSuperUser(superuserUsername);
toggleSuperuser.then().assertThat()
.statusCode(OK.getStatusCode());

Response createUser = UtilIT.createRandomUser();
createUser.prettyPrint();
assertEquals(200, createUser.getStatusCode());
String usernameOfUser = UtilIT.getUsernameFromResponse(createUser);
String userApiToken = UtilIT.getApiTokenFromResponse(createUser);

Response createDataverse1 = UtilIT.createRandomDataverse(superuserApiToken);
createDataverse1.prettyPrint();
createDataverse1.then().assertThat()
.statusCode(CREATED.getStatusCode());
String dataverseAlias1 = UtilIT.getAliasFromResponse(createDataverse1);

// create a second Dataverse and add a Group with permissions
Response createDataverse2 = UtilIT.createRandomDataverse(superuserApiToken);
createDataverse2.prettyPrint();
createDataverse2.then().assertThat()
.statusCode(CREATED.getStatusCode());
String dataverseAlias2 = UtilIT.getAliasFromResponse(createDataverse2);
String aliasInOwner = "groupFor" + dataverseAlias2;
String displayName = "Group for " + dataverseAlias2;
Response createGroup = UtilIT.createGroup(dataverseAlias2, aliasInOwner, displayName, superuserApiToken);
String groupIdentifier = JsonPath.from(createGroup.asString()).getString("data.identifier");
Response grantRoleResponse = UtilIT.grantRoleOnDataverse(dataverseAlias2, DataverseRole.EDITOR.toString(), groupIdentifier, superuserApiToken);
grantRoleResponse.prettyPrint();
grantRoleResponse.then().assertThat()
.statusCode(OK.getStatusCode());

Response collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, userApiToken, "ViewUnpublishedDataset");
assertEquals(403, collectionsResp.getStatusCode());
collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, "", "ViewUnpublishedDataset");
assertEquals(401, collectionsResp.getStatusCode());
collectionsResp = UtilIT.getUserPermittedCollections("fakeUser", superuserApiToken, "ViewUnpublishedDataset");
assertEquals(500, collectionsResp.getStatusCode());
collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, superuserApiToken, "bad");
assertEquals(500, collectionsResp.getStatusCode());

// Testing adding an explicit permission/role to one dataverse
collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, superuserApiToken, "DownloadFile");
collectionsResp.prettyPrint();
collectionsResp.then().assertThat()
.statusCode(OK.getStatusCode())
.body("data.count", equalTo(0));

Response assignRole = UtilIT.grantRoleOnDataverse(dataverseAlias1, DataverseRole.EDITOR.toString(),
"@" + usernameOfUser, superuserApiToken);
assignRole.prettyPrint();
assertEquals(200, assignRole.getStatusCode());

collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, superuserApiToken, "DownloadFile");
collectionsResp.prettyPrint();
collectionsResp.then().assertThat()
.statusCode(OK.getStatusCode())
.body("data.count", equalTo(1));

// Add user to group and test with both explicit and group permissions
Response addToGroup = UtilIT.addToGroup(dataverseAlias2, aliasInOwner, List.of("@" + usernameOfUser), superuserApiToken);
addToGroup.prettyPrint();
addToGroup.then().assertThat()
.statusCode(OK.getStatusCode());

collectionsResp = UtilIT.getUserPermittedCollections(usernameOfUser, superuserApiToken, "DownloadFile");
collectionsResp.prettyPrint();
collectionsResp.then().assertThat()
.statusCode(OK.getStatusCode())
.body("data.count", equalTo(2));
}

private Response convertUserFromBcryptToSha1(long idOfBcryptUserToConvert, String password) {
JsonObjectBuilder data = Json.createObjectBuilder();
data.add("builtinUserId", idOfBcryptUserToConvert);
Expand Down
9 changes: 9 additions & 0 deletions src/test/java/edu/harvard/iq/dataverse/api/UtilIT.java
Original file line number Diff line number Diff line change
Expand Up @@ -1344,6 +1344,15 @@ public static Response getUserTraces(String username, String apiToken) {
return response;
}

public static Response getUserPermittedCollections(String username, String apiToken, String permission) {
RequestSpecification requestSpecification = given();
if (!StringUtil.isEmpty(apiToken)) {
requestSpecification.header(API_TOKEN_HTTP_HEADER, apiToken);
}
Response response = requestSpecification.get("/api/users/" + username + "/allowedcollections/" + permission);
return response;
}

public static Response reingestFile(Long fileId, String apiToken) {
Response response = given()
.header(API_TOKEN_HTTP_HEADER, apiToken)
Expand Down

0 comments on commit cfd9da8

Please sign in to comment.