[DT-3107, DT-3108, DT-3110, DT-3112, DT-3113] Add entity-scoped FSO endpoints and access-controlled FileStorageObjectService#2867
Conversation
|
eweitz
left a comment
There was a problem hiding this comment.
Looks mostly good!
The only substantive question I have is in the comment beginning "Above notes".
Beyond that, I suggest some potential refinements for code clarity, but defer to you. I also flag code for "update" operations, which IIUC we'll remove pending Greg's confirmation.
| /** | ||
| * Downloads the binary content of a document. | ||
| * | ||
| * <ol> | ||
| * <li>Resolves entity ID. | ||
| * <li>Fetches FSO metadata (including category). | ||
| * <li>Calls {@link #checkAccess} with the file's actual category (READ). | ||
| * <li>Streams file from GCS. | ||
| * </ol> | ||
| */ |
There was a problem hiding this comment.
Incidentally, I'm curious about the pros / cons of Consent's pre-existing bucket-to-server-to-client approach vs. a the bucket-to-cilent approach used in Terra and SCP. Added as a parking lot topic for Tuesday!
| /** | ||
| * Updates the category of an existing document. | ||
| * | ||
| * <ol> | ||
| * <li>Parses and validates the new category for the entity. | ||
| * <li>Calls {@link #checkAccess} with the new category (CRUD). | ||
| * <li>Resolves entity ID, fetches FSO, updates category. | ||
| * </ol> | ||
| */ | ||
| public FileStorageObject updateDocumentCategory( |
There was a problem hiding this comment.
IIUC, undesired pending Greg's confirmation.
| * <li>CRUD: DAR creator only. | ||
| * <li>READ: ADMIN, CHAIRPERSON, MEMBER, or DAR creator. | ||
| * </ul> | ||
| * <li><b>DAC</b> – {@link FileCategory#DATA_ACCESS_AGREEMENT}: | ||
| * <ul> | ||
| * <li>CRUD: CHAIRPERSON of the specific DAC (entity-scoped) or ADMIN. | ||
| * <li>READ: any authenticated user. | ||
| * </ul> | ||
| * <li><b>DATASET</b> – {@link FileCategory#NIH_INSTITUTIONAL_CERTIFICATION}: | ||
| * <ul> | ||
| * <li>CRUD: ADMIN, DATASUBMITTER, or CHAIRPERSON. | ||
| * <li>READ: ADMIN, DATASUBMITTER, CHAIRPERSON, or MEMBER. | ||
| * </ul> | ||
| * <li><b>STUDY</b> – {@link FileCategory#ALTERNATIVE_DATA_SHARING_PLAN}: | ||
| * <ul> | ||
| * <li>CRUD: ADMIN, DATASUBMITTER, or CHAIRPERSON. | ||
| * <li>READ: ADMIN, DATASUBMITTER, or CHAIRPERSON (study must be DAC-linked; enforced by |
There was a problem hiding this comment.
Changing CRUD to CREATE, DELETE seems like it would be clearer, since we'll likely not allow "UPDATE" via API and "READ" is cover in the line below.
| ensureHasAnyRole( | ||
| user, UserRoles.ADMIN, UserRoles.DATASUBMITTER, UserRoles.CHAIRPERSON, UserRoles.MEMBER); |
There was a problem hiding this comment.
Above notes:
* <li><b>DATASET</b> – {@link FileCategory#NIH_INSTITUTIONAL_CERTIFICATION}:
* <ul>
* <li>CRUD: ADMIN, DATASUBMITTER, or CHAIRPERSON.
* <li>READ: ADMIN, DATASUBMITTER, CHAIRPERSON, or MEMBER.
* </ul>
* <li><b>STUDY</b> – {@link FileCategory#ALTERNATIVE_DATA_SHARING_PLAN}:
* <ul>
* <li>CRUD: ADMIN, DATASUBMITTER, or CHAIRPERSON.
* <li>READ: ADMIN, DATASUBMITTER, or CHAIRPERSON (study must be DAC-linked; enforced by
* entity resolution via DatasetService).
* </ul>
* </ul>
I interpret that to mean alternative data sharing plans should not permit READ to MEMBER. Naively, the UserRoles.MEMBER argument on this line seems like would permit READ to MEMBER.
| private void checkDarAccess(User user, String entityId, OperationType op) { | ||
| boolean isCreator = isDarCreator(user, entityId); | ||
|
|
||
| if (op == OperationType.CRUD && !isCreator) { |
There was a problem hiding this comment.
Following my other suggestion to refine "CRUD" (i.e. CREATE, READ, UPDATE, DELETE) to "CREATE, DELETE", I think refining this operation type to something like CREATE_OR_DELETE would help avoid maintainer confusion.
| description: Not Found | ||
|
|
||
| put: | ||
| summary: Update document category by entity and file id |
There was a problem hiding this comment.
IIUC we'll presumably remove this, pending Greg's confirmation.
| @@ -0,0 +1,15 @@ | |||
| type: object | |||
There was a problem hiding this comment.
IIUC we'll presumably remove this, pending Greg's approval.
| } | ||
|
|
||
| @Test | ||
| void testUpdateCategory() { |
There was a problem hiding this comment.
Flagging for removal pending Greg's confirmation.
| private static final String DAR_ID = "DAR-100"; | ||
|
|
||
| @Test | ||
| void crudAllowedForCreator() { |
There was a problem hiding this comment.
Similar to my other suggestions to refine usage of "CRUD" -- I recommend changing crud to e.g. createAndDelete throughout this PR's tests.
| ForbiddenException.class, | ||
| () -> service.checkAccess(researcher, "study", "1", null, OperationType.READ)); | ||
| } | ||
| } |
There was a problem hiding this comment.
Depending on the answer to my question in the comment beginning "Above notes", it might be worth testing that case here.



Addresses
https://broadworkbench.atlassian.net/browse/DT-3107
https://broadworkbench.atlassian.net/browse/DT-3108
https://broadworkbench.atlassian.net/browse/DT-3110
https://broadworkbench.atlassian.net/browse/DT-3112
https://broadworkbench.atlassian.net/browse/DT-3113
Summary
This PR introduces core API functionality for managing entity-scoped File Storage Objects (FSOs). It includes endpoints for uploading files, streaming file content, soft-deleting FSOs, and updating file categories. Additionally, it adds a centralized FileStorageObjectService to enforce consistent access control policies based on entity, category, and user roles across all FSO operations.
Have you read CONTRIBUTING.md lately? If not, do that first.