
edu.iu.uits.lms.provisioning.service.DeptProvFileUploadService Maven / Gradle / Ivy
package edu.iu.uits.lms.provisioning.service;
/*-
* #%L
* lms-lti-3rdpartyprovisioning
* %%
* Copyright (C) 2015 - 2022 Indiana University
* %%
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* 3. Neither the name of the Indiana University nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
import edu.iu.uits.lms.iuonly.model.DeptProvisioningUser;
import edu.iu.uits.lms.iuonly.services.DeptProvisioningUserServiceImpl;
import edu.iu.uits.lms.provisioning.config.BackgroundMessage;
import edu.iu.uits.lms.provisioning.config.BackgroundMessageSender;
import edu.iu.uits.lms.provisioning.controller.Constants;
import edu.iu.uits.lms.provisioning.model.FileUploadResult;
import edu.iu.uits.lms.provisioning.model.NotificationForm;
import edu.iu.uits.lms.provisioning.model.content.ByteArrayFileContent;
import edu.iu.uits.lms.provisioning.model.content.FileContent;
import edu.iu.uits.lms.provisioning.service.exception.FileParsingException;
import edu.iu.uits.lms.provisioning.service.exception.ZipException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.apache.commons.io.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@Slf4j
@Service
public class DeptProvFileUploadService {
@Autowired
private DeptRouter deptRouter;
@Autowired
private BackgroundMessageSender backgroundMessageSender;
@Autowired
private DeptProvisioningUserServiceImpl deptProvisioningUserService;
/**
*
* @param file Input zip file
* @return File object representing the directory where all the files were unzipped
* @throws IOException Throws IOException when there are file related issues
*/
public List unzip(MultipartFile file) throws IOException {
List files = new ArrayList<>();
byte[] buffer = new byte[1024];
File destDir = Files.createTempDirectory("ZipUpload").toFile();
ZipInputStream zis = new ZipInputStream(file.getInputStream());
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
File newFile = newFile(destDir, zipEntry);
File parent = newFile.getParentFile();
if (zipEntry.isDirectory()) {
if (!newFile.isDirectory() && !newFile.mkdirs()) {
throw new IOException("Failed to create directory " + newFile);
}
} else if (parent.getName().equals("__MACOSX")) {
//skip it
log.warn("Skipping over a file/folder we don't want to process: {}/{}", parent.getName(), newFile.getName());
} else {
// fix for Windows-created archives
if (!parent.isDirectory() && !parent.mkdirs()) {
throw new IOException("Failed to create directory " + parent);
}
// write file content
FileOutputStream fos = new FileOutputStream(newFile);
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
fos.close();
//Turn into multipart
FileItem fileItem = new DiskFileItem("mainFile", Files.probeContentType(newFile.toPath()),
false, newFile.getName(), (int) newFile.length(), newFile.getParentFile());
IOUtils.copy(new FileInputStream(newFile), fileItem.getOutputStream());
files.add(new CommonsMultipartFile(fileItem));
}
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
zis.close();
return files;
}
private File newFile(File destinationDir, ZipEntry zipEntry) throws IOException {
File destFile = new File(destinationDir, zipEntry.getName());
String destDirPath = destinationDir.getCanonicalPath();
String destFilePath = destFile.getCanonicalPath();
if (!destFilePath.startsWith(destDirPath + File.separator)) {
throw new IOException("Entry is outside of the target dir: " + zipEntry.getName());
}
return destFile;
}
/**
*
* @param files List of files to parse
* @param customUsersNotification Flag indicating if a custom user notification is desired
* @param department Department the upload is for
* @param principal Principal
* @param source Source of the upload
* @return ResponseEntity with the FileUploadResult
*/
public ResponseEntity parseFiles(MultipartFile[] files, boolean customUsersNotification,
String department, Object principal, Constants.SOURCE source) {
if (files == null || files.length == 0) {
return ResponseEntity.badRequest().body(new FileUploadResult("No files to process"));
}
try {
String validUsername = getValidatedUsername(principal);
MultiValuedMap filesByType = FileParsingUtil.parseFiles(files, customUsersNotification);
Long archiveId = deptRouter.zipOriginals(filesByType.get(DeptRouter.CSV_TYPES.ORIGINALS), department, validUsername);
NotificationForm notificationForm = parsePropertiesFile(filesByType.get(DeptRouter.CSV_TYPES.PROPERTIES));
if (customUsersNotification && notificationForm == null) {
FileUploadResult results = new FileUploadResult("User indicated that custom user notification was desired, but either no message.properties file was supplied, or it was malformed");
return ResponseEntity.badRequest().body(results);
}
backgroundMessageSender.send(new BackgroundMessage(filesByType, department, notificationForm, archiveId, validUsername, source));
} catch (FileParsingException | ZipException e) {
log.error("error parsing uploaded files", e);
FileUploadResult results = new FileUploadResult("Error parsing uploaded files", e.getFileErrors());
return ResponseEntity.badRequest().body(results);
} catch (UserAuthException e) {
log.error("Unable to get username for authz", e);
return ResponseEntity.status(e.getHttpStatus()).body(new FileUploadResult(e.getMessage()));
}
return ResponseEntity.ok(new FileUploadResult("The files are being processed. Summary emails will be sent at a later time."));
}
/**
*
* @param principal Principal object
* @return Validated Username
* @throws UserAuthException When user is not authorized
*/
protected String getValidatedUsername(Object principal) throws UserAuthException {
log.debug("Principal: {}", principal);
if (principal instanceof Jwt) {
Jwt jwt = ((Jwt) principal);
String username = jwt.getClaimAsString("user_name");
log.debug("Username from Jwt: {}", username);
if (username == null) {
//Fall back to the client id
username = jwt.getClaimAsString("client_id");
log.debug("client id from Jwt: {}", username);
}
DeptProvisioningUser user = deptProvisioningUserService.findByUsername(username);
log.debug("User: {}", user);
if (user != null) {
return user.getUsername();
} else {
throw new UserAuthException("User (" + username + ") is not authorized to upload files", username, HttpStatus.FORBIDDEN);
}
}
throw new UserAuthException("No username could be found for authorization", null, HttpStatus.UNAUTHORIZED);
}
private NotificationForm parsePropertiesFile(Collection propFiles) {
Iterator iterator = propFiles.iterator();
if (iterator.hasNext()) {
FileContent fc = iterator.next();
byte[] fileBytes = ((ByteArrayFileContent)fc).getContents();
CustomNotificationBuilder cnb = new CustomNotificationBuilder(new ByteArrayInputStream(fileBytes));
if (cnb.isFileExists() && cnb.isFileValid()) {
NotificationForm notificationForm = new NotificationForm();
notificationForm.setBody(cnb.getBody());
notificationForm.setSender(cnb.getSender());
notificationForm.setSubject(cnb.getSubject());
return notificationForm;
}
}
return null;
}
@Getter
public static class UserAuthException extends Exception {
private String username;
private HttpStatus httpStatus;
public UserAuthException(String message, String username, HttpStatus httpStatus) {
super(message);
this.username = username;
this.httpStatus = httpStatus;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy