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
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
## Bug ##
Preview URL users could not download files from the dataset being previewed if a guestbook was assigned to that dataset. This is now fixed. A similar issue with Locally FAIR content is also fixed.
41 changes: 15 additions & 26 deletions src/main/java/edu/harvard/iq/dataverse/api/Access.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@
import edu.harvard.iq.dataverse.authorization.DataverseRole;
import edu.harvard.iq.dataverse.authorization.Permission;
import edu.harvard.iq.dataverse.authorization.RoleAssignee;
import edu.harvard.iq.dataverse.authorization.users.ApiToken;
import edu.harvard.iq.dataverse.authorization.users.AuthenticatedUser;
import edu.harvard.iq.dataverse.authorization.users.GuestUser;
import edu.harvard.iq.dataverse.authorization.users.User;
import edu.harvard.iq.dataverse.authorization.users.*;
import edu.harvard.iq.dataverse.dataaccess.*;
import edu.harvard.iq.dataverse.datavariable.DataVariable;
import edu.harvard.iq.dataverse.datavariable.VariableServiceBean;
Expand Down Expand Up @@ -197,7 +194,7 @@ public BundleDownloadInstance datafileBundle(@Context ContainerRequestContext cr

if (gbrecs != true && df.isReleased()) {
// Write Guestbook record if not done previously and file is released
GuestbookResponse gbr = guestbookResponseService.initAPIGuestbookResponse(df.getOwner(), df, session, getRequestor(req.getUser()));
GuestbookResponse gbr = guestbookResponseService.initAPIGuestbookResponse(df.getOwner(), df, session, req.getUser());
guestbookResponseService.save(gbr);
MakeDataCountEntry entry = new MakeDataCountEntry(uriInfo, headers, dvRequestService, df);
mdcLogService.logEntry(entry);
Expand Down Expand Up @@ -327,7 +324,7 @@ public Response datafile(@Context ContainerRequestContext crc,

if (gbrecs != true && df.isReleased()){
// Write Guestbook record if not done previously and file is released
gbr = guestbookResponseService.initAPIGuestbookResponse(df.getOwner(), df, session, getRequestor(req.getUser()));
gbr = guestbookResponseService.initAPIGuestbookResponse(df.getOwner(), df, session, req.getUser());
}

DownloadInfo dInfo = new DownloadInfo(df);
Expand Down Expand Up @@ -1234,7 +1231,7 @@ private Response downloadDatafiles(ContainerRequestContext crc, String body, boo
String customZipServiceUrl = settingsService.getValueForKey(SettingsServiceBean.Key.CustomZipDownloadServiceUrl);
boolean useCustomZipService = customZipServiceUrl != null;

User user = getRequestor(getRequestUser(crc));
User user = getRequestUser(crc);
DataverseRequest req = createDataverseRequest(user);

Boolean getOrig = false;
Expand Down Expand Up @@ -2236,29 +2233,30 @@ public Response getUserPermissionsOnFile(@Context ContainerRequestContext crc,

private boolean checkGuestbookRequiredResponse(User user, UriInfo uriInfo, DataFile df, String gbrids) throws WebApplicationException {
// Check if guestbook response is required
Dataset d = df.getOwner();
boolean required = df.getOwner().hasEnabledGuestbook() && !d.getEffectiveGuestbookEntryAtRequest();
Dataset ds = df.getOwner();
boolean required = ds.hasEnabledGuestbook() && !ds.getEffectiveGuestbookEntryAtRequest();
boolean wasWrittenInPost = false;
if (required) {
User requestor = getRequestor(user);
if (requestor instanceof AuthenticatedUser && permissionService.userOn(requestor, df.getOwner()).has(Permission.EditDataset)) {
required = false;
checkAuthorization(user, df);
// PrivateUrlUsers are exempt from this requirement
if (user instanceof PrivateUrlUser) {
return false;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think you still need a check for Authenticated users though, since they may have gotten here with either ViewUnpublished (where they wouldn't make a gbr) or FIle download perms (where they would be required). (As you said, PrivateUrlUsers can skip that check because the checkAuth guarantees they have the ViewUnpublished perm).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

added checkAuthorization

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not seeing a change? I'd expect something like if ((user instanceof PrivateUrlUser) || (user instanceof AuthenticatedUser && permissionService.userOn(user, ds).has(Permission.ViewUnpublishedDataset)))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I see checkAuthorization - that assures that an AuthenticatedUser has either ViewUnpublished (which means no gbr required) or FileDownload (which would). So calling it here doesn't help distinguish the two cases and you need to explicitly check for the perm that would result in returning false.

}
// Check if we are downloading a thumbnail image which doesn't require a guestbook response
boolean imageThumb = uriInfo.getQueryParameters().containsKey("imageThumb");
if (imageThumb) {
required = false;
return false;
}

if (required && gbrids != null && !gbrids.isEmpty()) {
if (gbrids != null && !gbrids.isEmpty()) {
try {
// verify that this id is good
GuestbookResponse gbr = guestbookResponseService.findById(Long.valueOf(gbrids));
if (gbr == null) {
throw new NotFoundException("GuestbookResponse Not Found for id:" + gbrids);
}
Long delta = Instant.now().toEpochMilli() - gbr.getResponseTime().getTime();
wasWrittenInPost = gbr.getDataset().getId().equals(df.getOwner().getId()) && delta <= (GUESTBOOK_RESPONSE_SIGNEDURL_TIMEOUT_MINUTES * 60000L);
wasWrittenInPost = gbr.getDataset().getId().equals(ds.getId()) && delta <= (GUESTBOOK_RESPONSE_SIGNEDURL_TIMEOUT_MINUTES * 60000L);
} catch (NumberFormatException | DateTimeParseException ex) {
throw new BadRequestException(ex.getMessage());
}
Expand All @@ -2283,20 +2281,11 @@ private GuestbookResponse getGuestbookResponseFromBody(DataFile dataFile, String

// checkAuthorization is a convenience method; it calls the boolean method
// isAccessAuthorized(), the actual workhorse, and throws a 403 exception if not.
private void checkAuthorization(User initialUser, DataFile df) throws WebApplicationException {
User user = getRequestor(initialUser);
if (!isAccessAuthorized(user, df)) {
private void checkAuthorization(User requestUser, DataFile df) throws WebApplicationException {
if (!isAccessAuthorized(requestUser, df)) {
throw new ForbiddenException();
}
}
private User getRequestor(User user) {
// CompoundAuthMechanism should find the user by API Key/Token, Workflow, etc. And for SPA the Bearer Token
// For JSF check if CompoundAuthMechanism couldn't find the user then try to get it from the session
if (session!=null && user instanceof GuestUser) {
user = session.getUser();
}
return user;
}

private boolean isAccessAuthorized(User requestUser, DataFile df) {
// First, check if the file belongs to a released Dataset version:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,19 @@ public class SessionCookieAuthMechanism implements AuthMechanism {
@Inject
DataverseSession session;

public static final String ACCESS_PATH_PREFIX = "access/";

@Override
public User findUserFromRequest(ContainerRequestContext containerRequestContext) throws WrappedAuthErrorResponse {
if (FeatureFlags.API_SESSION_AUTH.enabled()) {
if (FeatureFlags.API_SESSION_AUTH.enabled() || isAccessApi(containerRequestContext)) {
return session.getUser();
}
return null;
}

private boolean isAccessApi(ContainerRequestContext containerRequestContext) {
String requestPath = containerRequestContext.getUriInfo() != null ? containerRequestContext.getUriInfo().getPath() : "";
return ("GET".equalsIgnoreCase(containerRequestContext.getMethod()) &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

At least the main Access Dataset /DownloadZip menu item calls the POST API, so PrivateURLUsers also need access to that. (Since the browser blocks other sites from sending our cookie for POST/PUT/etc. this shouldn't raise cross-site issues.)

requestPath.startsWith(ACCESS_PATH_PREFIX));
}
}
Loading