Skip to content

Commit

Permalink
refactor: 코드 취약점 보완 및 리팩토링 작업 완료 (#478)
Browse files Browse the repository at this point in the history
  • Loading branch information
limehee authored Aug 20, 2024
1 parent 3f8cd11 commit 451c476
Show file tree
Hide file tree
Showing 15 changed files with 416 additions and 189 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ private List<ActivityGroupBoard> getChildBoards(Long activityGroupBoardId) {
public ActivityGroupBoardChildResponseDto toActivityGroupBoardChildResponseDtoWithMemberInfo(ActivityGroupBoard activityGroupBoard) {
MemberBasicInfoDto memberBasicInfo = externalRetrieveMemberUseCase.getMemberBasicInfoById(activityGroupBoard.getMemberId());
List<ActivityGroupBoardChildResponseDto> childrenDtos = activityGroupBoard.getChildren().stream()
.map(child -> toActivityGroupBoardChildResponseDtoWithMemberInfo(child))
.map(this::toActivityGroupBoardChildResponseDtoWithMemberInfo)
.toList();
return ActivityGroupBoardChildResponseDto.toDto(activityGroupBoard, memberBasicInfo, childrenDtos);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.stereotype.Service;
import page.clab.api.global.common.email.domain.EmailTask;
import page.clab.api.global.common.email.domain.EmailTemplateType;
import page.clab.api.global.util.LogSanitizerUtil;

import java.io.File;
import java.io.IOException;
Expand All @@ -37,7 +38,7 @@ public class EmailAsyncService {

@Async
public void sendEmailAsync(String to, String subject, String content, List<File> files, EmailTemplateType emailTemplateType) throws MessagingException {
log.debug("Sending email to: {}", to);
log.debug("Sending email to: {}", LogSanitizerUtil.sanitizeForLog(to));
emailQueue.add(new EmailTask(to, subject, content, files, emailTemplateType));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
import page.clab.api.global.common.email.domain.EmailTemplateType;
import page.clab.api.global.common.email.dto.request.EmailDto;
import page.clab.api.global.common.email.exception.MessageSendingFailedException;
import page.clab.api.global.util.FileUtil;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;

@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -103,10 +103,10 @@ private List<File> convertMultipartFiles(List<MultipartFile> multipartFiles) {
private File convertMultipartFileToFile(MultipartFile multipartFile) {
String originalFilename = multipartFile.getOriginalFilename();
String extension = FilenameUtils.getExtension(originalFilename);
String path = filePath + File.separator + "temp" + File.separator + System.nanoTime() + "_" + UUID.randomUUID() + "." + extension;
String path = filePath + File.separator + "temp" + File.separator + FileUtil.makeFileName(extension);
path = path.replace("/", File.separator).replace("\\", File.separator);
File file = new File(path);
checkDir(file);
FileUtil.ensureParentDirectoryExists(file, filePath);

try {
multipartFile.transferTo(file);
Expand All @@ -125,13 +125,4 @@ private String generateEmailContent(EmailDto emailDto, String name) {
String emailTemplate = emailDto.getEmailTemplateType().getTemplateName();
return springTemplateEngine.process(emailTemplate, context);
}

private void checkDir(File file) {
if (!file.getParentFile().exists()) {
boolean isCreated = file.getParentFile().mkdirs();
if (!isCreated) {
log.error("Failed to create directory: {}", file.getParentFile().getAbsolutePath());
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,33 +1,24 @@
package page.clab.api.global.common.file.application;

import com.drew.imaging.ImageMetadataReader;
import com.drew.imaging.ImageProcessingException;
import com.drew.metadata.Directory;
import com.drew.metadata.Metadata;
import com.drew.metadata.MetadataException;
import com.drew.metadata.exif.ExifIFD0Directory;
import com.google.common.base.Strings;
import javax.imageio.ImageIO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FilenameUtils;
import org.imgscalr.Scalr;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import page.clab.api.global.common.file.exception.FileUploadFailException;
import page.clab.api.global.util.ImageCompressionUtil;
import page.clab.api.global.util.FileUtil;
import page.clab.api.global.util.ImageUtil;
import page.clab.api.global.util.LogSanitizerUtil;

import java.awt.image.BufferedImage;
import java.awt.image.BufferedImageOp;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;

@Component
@Configuration
Expand Down Expand Up @@ -55,158 +46,51 @@ public void init() {
filePath = filePath.replace("/", File.separator).replace("\\", File.separator);
}

public void saveQRCodeImage(byte[] image, String category, String saveFilename, String extension) throws IOException {
public void saveQRCodeImage(byte[] image, String category, String saveFilename, String extension, String baseDirectory) throws IOException {
init();
String savePath = filePath + File.separator + category + File.separator + saveFilename;
ByteArrayInputStream inputStream = new ByteArrayInputStream(image);
BufferedImage bufferedImage = ImageIO.read(inputStream);
File file = new File(savePath);
ensureParentDirectoryExists(file);
FileUtil.ensureParentDirectoryExists(file, baseDirectory);
ImageIO.write(bufferedImage, extension, file);
}

public String saveFile(MultipartFile multipartFile, String category) throws IOException {
public String saveFile(MultipartFile multipartFile, String category, String baseDirectory) throws IOException {
init();
String originalFilename = multipartFile.getOriginalFilename();
String extension = FilenameUtils.getExtension(originalFilename);
validateFileAttributes(originalFilename, extension);
FileUtil.validateFileAttributes(originalFilename, disallowExtensions);

String saveFilename = makeFileName(extension);
String saveFilename = FileUtil.makeFileName(extension);
String savePath = filePath + File.separator + category + File.separator + saveFilename;

File file = new File(savePath);
ensureParentDirectoryExists(file);
FileUtil.ensureParentDirectoryExists(file, baseDirectory);

try {
if (isImageFile(multipartFile)) {
BufferedImage originalImage = adjustImageDirection(multipartFile);
if (ImageUtil.isImageFile(multipartFile)) {
BufferedImage originalImage = ImageUtil.adjustImageDirection(multipartFile);
ImageIO.write(originalImage, Objects.requireNonNull(extension), file);
if (compressibleImageExtensions.contains(extension.toLowerCase())) {
ImageUtil.compressImage(filePath, savePath, imageQuality);
}
} else {
multipartFile.transferTo(file);
}
} catch (Exception e) {
throw new IOException("이미지의 뱡향을 조정하는데 오류가 발생했습니다.", e);
throw new IOException("이미지의 뱡향을 조정하는 데 오류가 발생했습니다.", e);
}

setFilePermissions(file, savePath, extension);
FileUtil.setFilePermissions(file, savePath, filePath);
return savePath;
}

private BufferedImage adjustImageDirection(MultipartFile multipartFile) throws Exception {
File tempFile = File.createTempFile("temp", null);
multipartFile.transferTo(tempFile);
int originalDirection = getImageDirection(tempFile);
BufferedImage bufferedImage = ImageIO.read(tempFile);

switch (originalDirection) {
case 1:
break;
case 3:
bufferedImage = Scalr.rotate(bufferedImage, Scalr.Rotation.CW_180, (BufferedImageOp[]) null);
break;
case 6:
bufferedImage = Scalr.rotate(bufferedImage, Scalr.Rotation.CW_90, (BufferedImageOp[]) null);
break;
case 8:
bufferedImage = Scalr.rotate(bufferedImage, Scalr.Rotation.CW_270, (BufferedImageOp[]) null);
break;
}

if (tempFile.exists() && !tempFile.delete()) {
throw new IOException("Failed to delete image file: " + tempFile.getAbsolutePath());
}

return bufferedImage;
}

public int getImageDirection(File tempFile) {
int originalDirection = 1;
try {
Metadata metadata = ImageMetadataReader.readMetadata(tempFile);
Directory directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
if (directory != null) {
originalDirection = directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
}
} catch (IOException e) {
log.error("이미지 파일을 읽는 중 IO 오류 발생: {}", e.getMessage());
} catch (ImageProcessingException e) {
log.error("이미지 파일 처리 중 오류 발생: {}", e.getMessage());
} catch (MetadataException e) {
log.error("이미지 파일의 메타데이터를 읽는 중 오류 발생: {}", e.getMessage());
} catch (Exception e) {
log.error("예기치 않은 오류 발생: {}", e.getMessage());
}
return originalDirection;
}

private boolean isImageFile(MultipartFile file) {
String contentType = file.getContentType();
return contentType != null && contentType.startsWith("image/");
}

private void validateFileAttributes(String originalFilename, String extension) throws FileUploadFailException {
if (!validateFilename(originalFilename)) {
throw new FileUploadFailException("허용되지 않은 파일명 : " + originalFilename);
}
if (!validateExtension(extension)) {
throw new FileUploadFailException("허용되지 않은 확장자 : " + originalFilename);
}
}

private boolean validateExtension(String extension) {
return !disallowExtensions.contains(extension.toLowerCase());
}

private boolean validateFilename(String fileName) {
return !Strings.isNullOrEmpty(fileName);
}

public String makeFileName(String extension) {
return (System.nanoTime() + "_" + UUID.randomUUID() + "." + extension);
}

private void ensureParentDirectoryExists(File file) {
if (!file.getParentFile().exists()) {
boolean isCreated = file.getParentFile().mkdirs();
if (!isCreated) {
log.error("Failed to create directory: {}", file.getParentFile().getAbsolutePath());
}
}
}

private void setFilePermissions(File file, String savePath, String extension) throws FileUploadFailException {
try {
String os = System.getProperty("os.name").toLowerCase();
if (compressibleImageExtensions.contains(extension.toLowerCase())) {
ImageCompressionUtil.compressImage(savePath, imageQuality);
}
if (os.contains("win")) {
boolean readOnly = file.setReadOnly();
if (!readOnly) {
log.error("Failed to set file read-only: {}", savePath);
}
} else {
setFilePermissionsUnix(savePath);
}
} catch (Exception e) {
throw new FileUploadFailException("Failed to upload file: " + savePath, e);
}
}

private void setFilePermissionsUnix(String filePath) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder("chmod", "400", filePath);
Process process = processBuilder.start();
int exitCode = process.waitFor();
if (exitCode != 0) {
log.error("Failed to set file permissions for: {}", filePath);
}
}

public void deleteFile(String savedPath) {
File fileToDelete = new File(savedPath);
boolean deleted = fileToDelete.delete();
if (!deleted) {
log.error("Failed to delete file: {}", savedPath);
log.error("Failed to delete file: {}", LogSanitizerUtil.sanitizeForLog(savedPath));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import page.clab.api.global.exception.NotFoundException;
import page.clab.api.global.exception.PermissionDeniedException;
import page.clab.api.global.util.FileSystemUtil;
import page.clab.api.global.util.FileUtil;

import java.io.File;
import java.io.IOException;
Expand Down Expand Up @@ -54,11 +55,11 @@ public String saveQRCodeImage(byte[] QRCodeImage, String path, long storagePerio
String currentMemberId = externalRetrieveMemberUseCase.getCurrentMemberId();
String extension = "png";
String originalFileName = path.replace(File.separator, "-") + nowDateTime;
String saveFilename = fileHandler.makeFileName(extension);
String saveFilename = FileUtil.makeFileName(extension);
String savePath = filePath + File.separator + path + File.separator + saveFilename;
String url = fileURL + "/" + path.replace(File.separator, "/") + "/" + saveFilename;

fileHandler.saveQRCodeImage(QRCodeImage, path, saveFilename, extension);
fileHandler.saveQRCodeImage(QRCodeImage, path, saveFilename, extension, filePath);
UploadedFile uploadedFile = UploadedFile.create(currentMemberId, originalFileName, saveFilename, savePath, url, (long) QRCodeImage.length, "image/png", storagePeriod, path);
uploadedFileService.saveUploadedFile(uploadedFile);
return url;
Expand All @@ -80,7 +81,7 @@ public UploadedFileResponseDto saveFile(MultipartFile multipartFile, String path
validateMemberCloudUsage(multipartFile, path);
checkAndRemoveExistingFile(path);

String savedFilePath = fileHandler.saveFile(multipartFile, path);
String savedFilePath = fileHandler.saveFile(multipartFile, path, filePath);
String fileName = new File(savedFilePath).getName();
String url = fileURL + "/" + path.replace(File.separator, "/") + "/" + fileName;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package page.clab.api.global.common.file.exception;

public class DirectoryCreationException extends RuntimeException {

public DirectoryCreationException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package page.clab.api.global.common.file.exception;

public class FilePermissionException extends RuntimeException {

public FilePermissionException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package page.clab.api.global.common.file.exception;

public class InvalidFileAttributeException extends RuntimeException {

public InvalidFileAttributeException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,10 @@
import page.clab.api.global.common.dto.ErrorResponse;
import page.clab.api.global.common.file.exception.AssignmentFileUploadFailException;
import page.clab.api.global.common.file.exception.CloudStorageNotEnoughException;
import page.clab.api.global.common.file.exception.DirectoryCreationException;
import page.clab.api.global.common.file.exception.FilePermissionException;
import page.clab.api.global.common.file.exception.FileUploadFailException;
import page.clab.api.global.common.file.exception.InvalidFileAttributeException;
import page.clab.api.global.common.slack.application.SlackService;
import page.clab.api.global.exception.CustomOptimisticLockingFailureException;
import page.clab.api.global.exception.DecryptionException;
Expand Down Expand Up @@ -96,6 +99,7 @@ public class GlobalExceptionHandler {
InvalidColumnException.class,
InvalidEmojiException.class,
InvalidRoleChangeException.class,
InvalidFileAttributeException.class,
RecruitmentNotActiveException.class,
RecruitmentEndDateExceededException.class,
StringIndexOutOfBoundsException.class,
Expand Down Expand Up @@ -192,6 +196,8 @@ public ErrorResponse<Exception> conflictException(HttpServletResponse response,
@ExceptionHandler({
IllegalStateException.class,
FileUploadFailException.class,
FilePermissionException.class,
DirectoryCreationException.class,
DataIntegrityViolationException.class,
IncorrectResultSizeDataAccessException.class,
ArrayIndexOutOfBoundsException.class,
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/page/clab/api/global/util/EncryptionUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ private byte[] generateRandomIV(int ivLengthBytes) {
}

private byte[] concat(byte[] a, byte[] b) {
if ((long) a.length + (long) b.length > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Resulting array size is too large to handle.");
}

byte[] combined = new byte[a.length + b.length];
System.arraycopy(a, 0, combined, 0, a.length);
System.arraycopy(b, 0, combined, a.length, b.length);
Expand Down
Loading

0 comments on commit 451c476

Please sign in to comment.