From 2165de9fc69c1c92664c7a2b4842c24be9225abd Mon Sep 17 00:00:00 2001 From: Yannick Marcon Date: Fri, 21 May 2021 17:33:53 +0200 Subject: [PATCH] #3627 File permissions checked and applied --- .../org/obiba/opal/web/FilesResource.java | 81 +++++++++++-------- .../provider/ForbiddenExceptionMapper.java | 42 ++++++++++ .../fs/presenter/FileExplorerPresenter.java | 26 +++--- .../web/gwt/app/client/i18n/Translations.java | 3 +- 4 files changed, 109 insertions(+), 43 deletions(-) create mode 100644 opal-core-ws/src/main/java/org/obiba/opal/web/provider/ForbiddenExceptionMapper.java diff --git a/opal-core-ws/src/main/java/org/obiba/opal/web/FilesResource.java b/opal-core-ws/src/main/java/org/obiba/opal/web/FilesResource.java index 42aadbcab4..f3f26df47a 100644 --- a/opal-core-ws/src/main/java/org/obiba/opal/web/FilesResource.java +++ b/opal-core-ws/src/main/java/org/obiba/opal/web/FilesResource.java @@ -385,6 +385,13 @@ public Response createFolder(@PathParam("path") String path, String folderName, } } + @POST + @Path("/{path:.*}") + @Consumes("application/json") + public Response processFile(@PathParam("path") String archivePath, @QueryParam("action") @DefaultValue("unzip") String action, @QueryParam("destination") String destinationPath, @QueryParam("key") String archiveKey, @Context UriInfo uriInfo) throws IOException { + return unzipArchive(archivePath, destinationPath, archiveKey, uriInfo); + } + @Nullable private Response validateFolder(FileObject folder, String path) throws IOException { if (folder == null || !folder.exists()) { @@ -457,45 +464,53 @@ public Response getAvailableCharsets() { return Response.ok(new JSONArray(names).toString()).build(); } - @POST - @Path("/_unzip/{path:.*}") - public Response unzipArchive(@PathParam("path") String archivePath, @QueryParam("destination") String destinationPath, @QueryParam("key") String archiveKey) throws IOException { - if (Strings.isNullOrEmpty(archivePath) || !archivePath.toLowerCase().endsWith(".zip") || Strings.isNullOrEmpty(destinationPath)) { - return Response.status(Status.BAD_REQUEST) - .entity("No destination path or valid archive file (ZIP) has been submitted. Please make sure that you are submitting them with your request.") - .build(); - } + private Response unzipArchive(String archivePath, String destinationPath, String archiveKey, UriInfo uriInfo) throws IOException { + if (Strings.isNullOrEmpty(archivePath) || !archivePath.toLowerCase().endsWith(".zip")) + throw new BadRequestException("Missing or invalid archive file (.zip)."); + if (Strings.isNullOrEmpty(destinationPath)) + throw new BadRequestException("Destination path is missing."); FileObject archive = resolveFileInFileSystem(archivePath); FileObject destination = resolveFileInFileSystem(destinationPath); - if (destination.exists() && !destination.getType().equals(FileType.FOLDER)) { - return Response.status(Status.BAD_REQUEST).entity("Destination path is a regular file.").build(); - } else if (!destination.exists()) { - destination.createFolder(); - if (!destination.exists()) - return Response.status(Status.INTERNAL_SERVER_ERROR).entity("cannotCreateFolderUnexpectedError").build(); - } - - if (archive.exists() && archive.getType().equals(FileType.FILE)) { - File archiveFile = opalRuntime.getFileSystem().getLocalFile(archive); - String archiveBasename = archiveFile.getName().replace(".zip", ""); - File destinationFolder = new File(opalRuntime.getFileSystem().getLocalFile(destination), archiveBasename); - int inc = 1; - while (destinationFolder.exists()) { - destinationFolder = new File(destinationFolder.getParentFile(), archiveBasename + "-" + inc); - inc++; + if (!archive.exists()) + throw new BadRequestException("Archive file is missing."); + else if (!archive.isReadable()) + throw new ForbiddenException("Archive is not readable."); + else if (!archive.getType().equals(FileType.FILE)) + throw new BadRequestException("Archive file is not a regular file."); + + if (destination.exists()) { + if (!destination.getType().equals(FileType.FOLDER)) + throw new BadRequestException("Destination is not a folder."); + } else if (!destination.exists() && destination.getParent().isWriteable()) { + try { + destination.createFolder(); + } catch (FileSystemException e) { + throw new InternalServerErrorException("Destination folder cannot be created."); } - if (Strings.isNullOrEmpty(archiveKey)) { - org.obiba.core.util.FileUtil.unzip(archiveFile, destinationFolder); - } else { - org.obiba.core.util.FileUtil.unzip(archiveFile, destinationFolder, archiveKey); - } - } else { - return Response.status(Status.NOT_FOUND).entity("No archive file found.").build(); } - - return Response.ok(destinationPath).build(); + if (!destination.isWriteable()) + throw new ForbiddenException("Destination folder is not writable."); + + File archiveFile = opalRuntime.getFileSystem().getLocalFile(archive); + String archiveBasename = archiveFile.getName().replace(".zip", ""); + File destinationFolder = new File(opalRuntime.getFileSystem().getLocalFile(destination), archiveBasename); + int inc = 1; + while (destinationFolder.exists()) { + destinationFolder = new File(destinationFolder.getParentFile(), archiveBasename + "-" + inc); + inc++; + } + if (Strings.isNullOrEmpty(archiveKey)) + org.obiba.core.util.FileUtil.unzip(archiveFile, destinationFolder); + else + org.obiba.core.util.FileUtil.unzip(archiveFile, destinationFolder, archiveKey); + + Opal.FileDto dto = getBaseFolderBuilder(destination.getChild(destinationFolder.getName())).build(); + URI folderUri = uriInfo.getBaseUriBuilder().path(FilesResource.class).path(destinationPath).path(destinationFolder.getName()).build(); + return Response.created(folderUri)// + .header(AuthorizationInterceptor.ALT_PERMISSIONS, new OpalPermissions(folderUri, AclAction.FILES_ALL))// + .entity(dto).build(); } // diff --git a/opal-core-ws/src/main/java/org/obiba/opal/web/provider/ForbiddenExceptionMapper.java b/opal-core-ws/src/main/java/org/obiba/opal/web/provider/ForbiddenExceptionMapper.java new file mode 100644 index 0000000000..c3f4604da2 --- /dev/null +++ b/opal-core-ws/src/main/java/org/obiba/opal/web/provider/ForbiddenExceptionMapper.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2021 OBiBa. All rights reserved. + * + * This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.obiba.opal.web.provider; + +import com.google.protobuf.GeneratedMessage; +import org.obiba.opal.web.magma.ClientErrorDtos; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.ws.rs.ForbiddenException; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.Provider; + +import static javax.ws.rs.core.Response.Status.FORBIDDEN; + +@Component +@Provider +public class ForbiddenExceptionMapper extends ErrorDtoExceptionMapper { + + private static final Logger log = LoggerFactory.getLogger(ForbiddenExceptionMapper.class); + + @Override + protected Response.Status getStatus() { + return FORBIDDEN; + } + + @Override + protected GeneratedMessage.ExtendableMessage getErrorDto(ForbiddenException exception) { + log.warn("Forbidden exception", exception); + return ClientErrorDtos.getErrorMessage(getStatus(), "Forbidden", exception); + } + +} diff --git a/opal-gwt-client/src/main/java/org/obiba/opal/web/gwt/app/client/fs/presenter/FileExplorerPresenter.java b/opal-gwt-client/src/main/java/org/obiba/opal/web/gwt/app/client/fs/presenter/FileExplorerPresenter.java index e444bc54d4..bca9848afb 100644 --- a/opal-gwt-client/src/main/java/org/obiba/opal/web/gwt/app/client/fs/presenter/FileExplorerPresenter.java +++ b/opal-gwt-client/src/main/java/org/obiba/opal/web/gwt/app/client/fs/presenter/FileExplorerPresenter.java @@ -24,6 +24,7 @@ import org.obiba.opal.web.gwt.app.client.fs.event.FolderUpdatedEvent; import org.obiba.opal.web.gwt.app.client.fs.event.UnzipRequestEvent; import org.obiba.opal.web.gwt.app.client.i18n.TranslationMessages; +import org.obiba.opal.web.gwt.app.client.i18n.Translations; import org.obiba.opal.web.gwt.app.client.presenter.ModalProvider; import org.obiba.opal.web.gwt.app.client.presenter.SplitPaneWorkbenchPresenter; import org.obiba.opal.web.gwt.rest.client.ResourceAuthorizationRequestBuilderFactory; @@ -59,6 +60,8 @@ private enum FileAction { private final TranslationMessages translationMessages; + private final Translations translations; + private final ModalProvider fileUploadModalProvider; private final ModalProvider createFolderModalProvider; @@ -80,16 +83,17 @@ private enum FileAction { @Inject @SuppressWarnings("PMD.ExcessiveParameterList") public FileExplorerPresenter(Display display, EventBus eventBus, FilePathPresenter filePathPresenter, - FilePlacesPresenter filePlacesPresenter, FolderDetailsPresenter folderDetailsPresenter, - ModalProvider fileUploadModalProvider, - ModalProvider createFolderModalProvider, TranslationMessages translationMessages, - ModalProvider encryptDownloadModalProvider, - ModalProvider renameModalPresenterModalProvider, - ModalProvider unzipModalPresenterModalProvider) { + FilePlacesPresenter filePlacesPresenter, FolderDetailsPresenter folderDetailsPresenter, + Translations translations, ModalProvider fileUploadModalProvider, + ModalProvider createFolderModalProvider, TranslationMessages translationMessages, + ModalProvider encryptDownloadModalProvider, + ModalProvider renameModalPresenterModalProvider, + ModalProvider unzipModalPresenterModalProvider) { super(eventBus, display); this.filePathPresenter = filePathPresenter; this.filePlacesPresenter = filePlacesPresenter; this.folderDetailsPresenter = folderDetailsPresenter; + this.translations = translations; this.translationMessages = translationMessages; this.fileUploadModalProvider = fileUploadModalProvider.setContainer(this); this.createFolderModalProvider = createFolderModalProvider.setContainer(this); @@ -211,9 +215,10 @@ public void onResponseCode(Request request, Response response) { public void onUnzipRequest(UnzipRequestEvent event) { final String password = event.getPassword(); - String requestUrl = "/files/_unzip" + event.getArchive(); + String requestUrl = "/files" + event.getArchive(); UriBuilder uriBuilder = UriBuilder.create().fromPath(requestUrl); + uriBuilder.query("action", "unzip"); uriBuilder.query("destination", event.getDestination()); if (password != null && password.trim().length() > 1) { @@ -224,16 +229,19 @@ public void onUnzipRequest(UnzipRequestEvent event) { @Override public void onResponseCode(Request request, Response response) { - if(response.getStatusCode() != Response.SC_OK) { + if(response.getStatusCode() != Response.SC_CREATED) { getEventBus().fireEvent(NotificationEvent.newBuilder().error(response.getText()).build()); } else { + FileDto created = FileDto.parse(response.getText()); + getEventBus().fireEvent(NotificationEvent.newBuilder() + .success(translations.userMessageMap().get("FolderCreated").replace("{0}", created.getPath())).build()); getEventBus().fireEvent(new FolderRefreshEvent(getCurrentFolder())); } } }; ResourceRequestBuilderFactory.newBuilder().forResource(uriBuilder.build()).post() - .withCallback(Response.SC_OK, responseCodeCallback) + .withCallback(Response.SC_CREATED, responseCodeCallback) .send(); } }); diff --git a/opal-gwt-client/src/main/java/org/obiba/opal/web/gwt/app/client/i18n/Translations.java b/opal-gwt-client/src/main/java/org/obiba/opal/web/gwt/app/client/i18n/Translations.java index eb3b9847b4..2a42a0cba5 100644 --- a/opal-gwt-client/src/main/java/org/obiba/opal/web/gwt/app/client/i18n/Translations.java +++ b/opal-gwt-client/src/main/java/org/obiba/opal/web/gwt/app/client/i18n/Translations.java @@ -761,7 +761,8 @@ public interface Translations extends Constants { "GHOrganizationIsRequired", "GitHub user or organization name is required", "ResourceAssignSuccess", "Resource assignment in R was successful.", "ResourceAssignFailed", "Resource assignment in R has failed: ", - "SQLError", "{0}" + "SQLError", "{0}", + "FolderCreated", "Folder created: {0}" }) Map userMessageMap();