-
Notifications
You must be signed in to change notification settings - Fork 8
refactor: 관리자 대학 이미지 업로드 경로 추가 #760
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| package com.example.solidconnection.s3.domain; | ||
|
|
||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import com.example.solidconnection.common.exception.ErrorCode; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.security.MessageDigest; | ||
| import java.security.NoSuchAlgorithmException; | ||
| import java.util.HexFormat; | ||
|
|
||
| public final class UploadDirectoryName { | ||
|
|
||
| private static final int HASH_PREFIX_LENGTH = 12; | ||
|
|
||
| private UploadDirectoryName() { | ||
| } | ||
|
|
||
| public static String fromUniversityEnglishName(String englishName) { | ||
| if (englishName == null || englishName.isBlank()) { | ||
| throw new CustomException(ErrorCode.INVALID_INPUT); | ||
| } | ||
|
|
||
| String directoryName = englishName.trim() | ||
| .toLowerCase() | ||
| .replaceAll("\\s*&\\s*", "_and_") | ||
| .replaceAll("\\s+", "_") | ||
| .replaceAll("_+", "_") | ||
| .replaceAll("[^a-z0-9_-]", ""); | ||
|
|
||
| if (directoryName.isBlank()) { | ||
| throw new CustomException(ErrorCode.INVALID_INPUT); | ||
| } | ||
|
|
||
| return directoryName + "_" + hash(englishName.trim()); | ||
| } | ||
|
|
||
| private static String hash(String value) { | ||
| try { | ||
| MessageDigest messageDigest = MessageDigest.getInstance("SHA-256"); | ||
| byte[] digest = messageDigest.digest(value.getBytes(StandardCharsets.UTF_8)); | ||
| return HexFormat.of().formatHex(digest).substring(0, HASH_PREFIX_LENGTH); | ||
| } catch (NoSuchAlgorithmException e) { | ||
| throw new CustomException(ErrorCode.NOT_DEFINED_ERROR); | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,23 +3,28 @@ | |
| import com.example.solidconnection.common.constant.FileConstants; | ||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import com.example.solidconnection.common.exception.ErrorCode; | ||
| import java.util.List; | ||
| import lombok.Getter; | ||
|
|
||
| @Getter | ||
| public enum UploadPath { | ||
| PROFILE("profile"), | ||
| GPA("gpa"), | ||
| LANGUAGE_TEST("language"), | ||
| COMMUNITY("community"), | ||
| NEWS("news"), | ||
| CHAT("chat/files"), | ||
| MENTOR_PROOF("mentor-proof"), | ||
| PROFILE("profile", true), | ||
| GPA("gpa", false), | ||
| LANGUAGE_TEST("language", false), | ||
| COMMUNITY("community", true), | ||
| NEWS("news", true), | ||
| CHAT("chat/files", false), | ||
| MENTOR_PROOF("mentor-proof", false), | ||
| ADMIN_UNIVERSITY_LOGO("admin/logo", true), | ||
| ADMIN_UNIVERSITY_BACKGROUND("admin/background", true) | ||
| ; | ||
|
|
||
| private final String type; | ||
| private final boolean imageOnly; | ||
|
|
||
| UploadPath(String type) { | ||
| UploadPath(String type, boolean imageOnly) { | ||
| this.type = type; | ||
| this.imageOnly = imageOnly; | ||
| } | ||
|
|
||
| public boolean isResizable(long fileSize, String extension, long maxSizeBytes) { | ||
|
|
@@ -35,7 +40,7 @@ public boolean isResizable(long fileSize, String extension, long maxSizeBytes) { | |
| } | ||
|
|
||
| public void validateExtension(String extension) { | ||
| if (extension == null || !FileConstants.ALL_ALLOWED_EXTENSIONS.contains(extension.toLowerCase())) { | ||
| if (extension == null || !getAllowedExtensions().contains(extension.toLowerCase())) { | ||
| throw new CustomException(ErrorCode.NOT_ALLOWED_FILE_EXTENSIONS, | ||
| "허용된 형식: " + getAllowedExtensionsMessage()); | ||
| } | ||
|
Comment on lines
42
to
46
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 1) 확장자-only 검증은 우회 업로드를 막지 못합니다. Line 43 기준으로 현재 검증은 파일명 확장자만 신뢰합니다. 이 방식은 내용이 다른 파일(예: 실행파일/스크립트)을 확장자만 바꿔 통과시킬 수 있어요. 최소한 매직바이트(파일 시그니처) 기반 검증을 추가하고, 확장자 검증은 보조 수단으로 두는 편이 안전합니다. 🔧 제안 패치 (개념 예시) public void validateExtension(String extension) {
if (extension == null || !getAllowedExtensions().contains(extension.toLowerCase())) {
throw new CustomException(ErrorCode.NOT_ALLOWED_FILE_EXTENSIONS,
"허용된 형식: " + getAllowedExtensionsMessage());
}
}
+
+// S3Service.validateFile(...)에서 확장자 검증 후 추가:
+// - imageOnly 경로: JPEG/PNG/WEBP 등 허용 포맷 시그니처 검사
+// - 비 imageOnly 경로: MIME + 시그니처 기반 최소 검증🤖 Prompt for AI Agents
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 나주에 수정할게 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
원하시면 "이슈 만들어줘"라고 말씀해 주세요 😊 (๑˃ᵕ˂)و ✨ |
||
|
|
@@ -46,6 +51,13 @@ public boolean isImage(String extension) { | |
| } | ||
|
|
||
| public String getAllowedExtensionsMessage() { | ||
| return String.join(", ", FileConstants.ALL_ALLOWED_EXTENSIONS); | ||
| return String.join(", ", getAllowedExtensions()); | ||
| } | ||
|
|
||
| private List<String> getAllowedExtensions() { | ||
| if (imageOnly) { | ||
| return FileConstants.IMAGE_EXTENSIONS; | ||
| } | ||
| return FileConstants.ALL_ALLOWED_EXTENSIONS; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| ALTER TABLE host_university | ||
| ADD CONSTRAINT uk_host_university_english_name UNIQUE (english_name); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package com.example.solidconnection.s3.domain; | ||
|
|
||
| import static org.assertj.core.api.Assertions.assertThat; | ||
| import static org.assertj.core.api.Assertions.assertThatThrownBy; | ||
|
|
||
| import com.example.solidconnection.common.exception.CustomException; | ||
| import org.junit.jupiter.api.DisplayName; | ||
| import org.junit.jupiter.api.Nested; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| @DisplayName("업로드 디렉토리명 테스트") | ||
| class UploadDirectoryNameTest { | ||
|
|
||
| @Nested | ||
| class 대학_영문명_변환_테스트 { | ||
|
|
||
| @Test | ||
| void 대학_영문명의_공백을_언더스코어로_변환한다() { | ||
| // given | ||
| String englishName = "University of Tokyo"; | ||
|
|
||
| // when | ||
| String directoryName = UploadDirectoryName.fromUniversityEnglishName(englishName); | ||
|
|
||
| // then | ||
| assertThat(directoryName) | ||
| .startsWith("university_of_tokyo_") | ||
| .matches("university_of_tokyo_[0-9a-f]{12}"); | ||
| } | ||
|
|
||
| @Test | ||
| void 특수문자를_제거하고_앰퍼샌드는_and로_변환한다() { | ||
| // given | ||
| String englishName = "Texas A&M University, Austin"; | ||
|
|
||
| // when | ||
| String directoryName = UploadDirectoryName.fromUniversityEnglishName(englishName); | ||
|
|
||
| // then | ||
| assertThat(directoryName) | ||
| .startsWith("texas_a_and_m_university_austin_") | ||
| .matches("texas_a_and_m_university_austin_[0-9a-f]{12}"); | ||
| } | ||
|
|
||
| @Test | ||
| void 같은_slug로_변환되는_서로_다른_영문명은_다른_디렉토리명을_반환한다() { | ||
| // given | ||
| String englishName = "Texas A&M University"; | ||
| String normalizedCollisionName = "Texas A and M University"; | ||
|
|
||
| // when | ||
| String directoryName = UploadDirectoryName.fromUniversityEnglishName(englishName); | ||
| String collisionDirectoryName = UploadDirectoryName.fromUniversityEnglishName(normalizedCollisionName); | ||
|
|
||
| // then | ||
| assertThat(directoryName).isNotEqualTo(collisionDirectoryName); | ||
| } | ||
|
|
||
| @Test | ||
| void 공백_문자열이면_예외가_발생한다() { | ||
| // given | ||
| String blankName = " "; | ||
|
|
||
| // when & then | ||
| assertThatThrownBy(() -> UploadDirectoryName.fromUniversityEnglishName(blankName)) | ||
| .isInstanceOf(CustomException.class); | ||
| } | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.