-
Notifications
You must be signed in to change notification settings - Fork 0
커스텀 복습 주기 소유권 검증 누락 수정 #71
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
Conversation
🧪 테스트 커버리지 리포트
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR addresses a security vulnerability where users could reference custom review cycles they don't own when creating a review. The fix adds ownership validation in ReviewService to ensure only the cycle owner can use their custom cycles.
Changes:
- Added ownership validation logic to
ReviewService.saveReview()for custom cycle selections - Added
CycleOptionRepositorydependency to enable cycle lookup and validation - Added comprehensive test coverage for ownership validation scenarios (owner, not found, not owner)
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 7 comments.
| File | Description |
|---|---|
| src/main/java/com/recyclestudy/review/service/ReviewService.java | Added validateCycleSelectionOwnership method and CycleOptionRepository dependency to validate custom cycle ownership before processing reviews |
| src/test/java/com/recyclestudy/review/service/ReviewServiceTest.java | Added three test cases covering successful custom cycle usage, non-existent cycle handling, and unauthorized access attempts |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| given(memberRepository.findByIdentifier(identifier)).willReturn(Optional.of(member)); | ||
| given(reviewRepository.save(any(Review.class))).willReturn(review); | ||
| given(cycleOptionRepository.findById(cycleOptionId)).willReturn(Optional.empty()); |
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test demonstrates the bug: This test mocks reviewRepository.save to succeed (line 227) before the validation failure occurs. Because the actual implementation saves the review before validating ownership (lines 50-54 in ReviewService), this test will leave an orphaned Review in the database when run against a real database. Once the bug in ReviewService is fixed to validate before saving, this mock setup should be removed since reviewRepository.save should never be called when validation fails.
| given(memberRepository.findByIdentifier(identifier)).willReturn(Optional.of(member)); | ||
| given(reviewRepository.save(any(Review.class))).willReturn(review); | ||
| given(cycleOptionRepository.findById(cycleOptionId)).willReturn(Optional.of(cycleOption)); |
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test demonstrates the bug: Similar to the notFoundCycleOption test, this test mocks reviewRepository.save to succeed (line 250) before the validation failure. Once the bug in ReviewService is fixed to validate ownership before saving the review, this mock setup should be removed since reviewRepository.save should never be called when ownership validation fails.
| final Review savedReview = reviewRepository.save(review); | ||
| log.info("[REVIEW_SAVED] 복습 주제 저장 성공: reviewId={}", savedReview.getId()); | ||
|
|
||
| validateCycleSelectionOwnership(input.cycle(), member); | ||
| final List<LocalDateTime> scheduledAts = calculateScheduledAts(input.cycle(), member); | ||
|
|
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical ordering issue: The review is saved to the database (line 51) before ownership validation occurs (line 54). If the validation fails, an orphaned Review entity will remain in the database without any associated ReviewCycles or NotificationHistory. The validation should be performed before persisting the Review entity to prevent data inconsistency.
| final Review savedReview = reviewRepository.save(review); | |
| log.info("[REVIEW_SAVED] 복습 주제 저장 성공: reviewId={}", savedReview.getId()); | |
| validateCycleSelectionOwnership(input.cycle(), member); | |
| final List<LocalDateTime> scheduledAts = calculateScheduledAts(input.cycle(), member); | |
| validateCycleSelectionOwnership(input.cycle(), member); | |
| final List<LocalDateTime> scheduledAts = calculateScheduledAts(input.cycle(), member); | |
| final Review savedReview = reviewRepository.save(review); | |
| log.info("[REVIEW_SAVED] 복습 주제 저장 성공: reviewId={}", savedReview.getId()); |
| .orElseThrow(() -> new NotFoundException("존재하지 않는 복습 주기입니다")); | ||
|
|
||
| if (!cycleOption.isOwner(member)) { | ||
| throw new NotFoundException("존재하지 않는 복습 주기입니다"); |
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error message inconsistency: The error message "존재하지 않는 복습 주기입니다" differs from the pattern used elsewhere in the codebase. In CycleOptionService (lines 66, 81, 97 in cycle/service/CycleOptionService.java) and CustomCycleSelectionResolver (line 27 in cycle/service/resolver/CustomCycleSelectionResolver.java), the message is "존재하지 않는 주기 옵션입니다." with a period. For consistency with the existing codebase patterns, use "존재하지 않는 주기 옵션입니다." instead.
| .orElseThrow(() -> new NotFoundException("존재하지 않는 복습 주기입니다")); | |
| if (!cycleOption.isOwner(member)) { | |
| throw new NotFoundException("존재하지 않는 복습 주기입니다"); | |
| .orElseThrow(() -> new NotFoundException("존재하지 않는 주기 옵션입니다.")); | |
| if (!cycleOption.isOwner(member)) { | |
| throw new NotFoundException("존재하지 않는 주기 옵션입니다."); |
| .orElseThrow(() -> new NotFoundException("존재하지 않는 복습 주기입니다")); | ||
|
|
||
| if (!cycleOption.isOwner(member)) { | ||
| throw new NotFoundException("존재하지 않는 복습 주기입니다"); |
Copilot
AI
Feb 13, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Error message inconsistency: Same as above - use "존재하지 않는 주기 옵션입니다." (with period) to match the pattern in CycleOptionService.validateOwnership (line 97 in cycle/service/CycleOptionService.java) which throws NotFoundException with this exact message when ownership validation fails.
| throw new NotFoundException("존재하지 않는 복습 주기입니다"); | |
| throw new NotFoundException("존재하지 않는 주기 옵션입니다."); |
🚀 작업 내용
ReviewService에 커스텀 복습 주기 소유권 검증 로직을 추가cycle이CustomCycleSelection인 경우에만 주기 소유권 검증 수행CycleOptionRepository의존성 추가NotFoundException반환📸 이슈 번호
✍ 궁금한 점