-
Notifications
You must be signed in to change notification settings - Fork 0
[FIX] 캠페인 제안 양방향 고려 이벤트 및 분리 설계 #394
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 | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -41,7 +41,7 @@ public void handleCampaignProposalSent(CampaignProposalSentEvent event) { | |||||||||||||||||||||
| event.proposalId(), event.isReProposal()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // 채팅방이 없으면 생성 | ||||||||||||||||||||||
| Long roomId = ensureRoomAndGetId(event.brandUserId(), event.creatorUserId()); | ||||||||||||||||||||||
| Long roomId = ensureRoomAndGetId(event); | ||||||||||||||||||||||
| ChatProposalCardPayloadResponse payload = createPayload(event); | ||||||||||||||||||||||
| String eventId = ProposalSentEvent.generateEventId(event.proposalId(), event.isReProposal()); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
@@ -80,11 +80,11 @@ private ChatProposalDecisionStatus toChatDecisionStatus(ProposalStatus proposalS | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| /** | ||||||||||||||||||||||
| * 채팅방이 없으면 생성하고, roomId를 반환합니다. | ||||||||||||||||||||||
| * 이 리스너는 AFTER_COMMIT 컨텍스트에서 실행되므로 createOrGetRoom이 별도 트랜잭션으로 처리됩니다. | ||||||||||||||||||||||
| * 이벤트 기반 자동 생성이므로 createOrGetRoomSystem 사용 (권한 검증 없음). | ||||||||||||||||||||||
| */ | ||||||||||||||||||||||
| private Long ensureRoomAndGetId(Long brandUserId, Long creatorUserId) { | ||||||||||||||||||||||
| private Long ensureRoomAndGetId(CampaignProposalSentEvent event) { | ||||||||||||||||||||||
| return chatRoomCommandService | ||||||||||||||||||||||
| .createOrGetRoom(brandUserId, brandUserId, creatorUserId) | ||||||||||||||||||||||
| .createOrGetRoomSystem(event.brandUserId(), event.creatorUserId()) | ||||||||||||||||||||||
| .roomId(); | ||||||||||||||||||||||
|
Comment on lines
+85
to
88
Contributor
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. The
Suggested change
|
||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| import org.slf4j.LoggerFactory; | ||
| import org.springframework.dao.DataIntegrityViolationException; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Propagation; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
|
|
||
| import com.example.RealMatch.chat.application.cache.ChatCacheInvalidationService; | ||
|
|
@@ -35,9 +36,34 @@ public class ChatRoomCommandServiceImpl implements ChatRoomCommandService { | |
|
|
||
| @Override | ||
| @Transactional | ||
| public ChatRoomCreateResponse createOrGetRoom(Long userId, Long brandId, Long creatorId) { | ||
| validateRequest(userId, brandId, creatorId); | ||
| public ChatRoomCreateResponse createOrGetRoomAsMember(Long userId, Long brandId, Long creatorId) { | ||
| validateMemberRequest(userId, brandId, creatorId); | ||
| return findOrCreateRoom(brandId, creatorId); | ||
| } | ||
|
|
||
| @Override | ||
| @Transactional(propagation = Propagation.REQUIRES_NEW) | ||
| public ChatRoomCreateResponse createOrGetRoomSystem(Long brandId, Long creatorId) { | ||
| validateSystemRequest(brandId, creatorId); | ||
| return findOrCreateRoom(brandId, creatorId); | ||
| } | ||
|
Comment on lines
+44
to
+49
Contributor
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. The new method |
||
|
|
||
| private void validateMemberRequest(Long userId, Long brandId, Long creatorId) { | ||
| if (brandId == null || creatorId == null || brandId.equals(creatorId)) { | ||
| throw new CustomException(ChatErrorCode.INVALID_ROOM_REQUEST); | ||
| } | ||
| if (!userId.equals(brandId) && !userId.equals(creatorId)) { | ||
| throw new CustomException(ChatErrorCode.NOT_ROOM_MEMBER); | ||
| } | ||
| } | ||
|
|
||
| private void validateSystemRequest(Long brandId, Long creatorId) { | ||
| if (brandId == null || creatorId == null || brandId.equals(creatorId)) { | ||
| throw new CustomException(ChatErrorCode.INVALID_ROOM_REQUEST); | ||
| } | ||
| } | ||
|
Comment on lines
+51
to
+64
Contributor
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.
private void validateRoomParticipants(Long brandId, Long creatorId) {
if (brandId == null || creatorId == null || brandId.equals(creatorId)) {
throw new CustomException(ChatErrorCode.INVALID_ROOM_REQUEST);
}
}
private void validateMemberRequest(Long userId, Long brandId, Long creatorId) {
validateRoomParticipants(brandId, creatorId);
if (!userId.equals(brandId) && !userId.equals(creatorId)) {
throw new CustomException(ChatErrorCode.NOT_ROOM_MEMBER);
}
}
private void validateSystemRequest(Long brandId, Long creatorId) {
validateRoomParticipants(brandId, creatorId);
}References
|
||
|
|
||
| private ChatRoomCreateResponse findOrCreateRoom(Long brandId, Long creatorId) { | ||
| String roomKey = ChatRoomKeyGenerator.createDirectRoomKey(brandId, creatorId); | ||
|
|
||
| ChatRoom room = chatRoomRepository.findByRoomKey(roomKey).orElse(null); | ||
|
|
@@ -52,15 +78,6 @@ public ChatRoomCreateResponse createOrGetRoom(Long userId, Long brandId, Long cr | |
| ); | ||
| } | ||
|
|
||
| private void validateRequest(Long userId, Long brandId, Long creatorId) { | ||
| if (brandId == null || creatorId == null || brandId.equals(creatorId)) { | ||
| throw new CustomException(ChatErrorCode.INVALID_ROOM_REQUEST); | ||
| } | ||
| if (!userId.equals(brandId) && !userId.equals(creatorId)) { | ||
| throw new CustomException(ChatErrorCode.NOT_ROOM_MEMBER); | ||
| } | ||
| } | ||
|
|
||
| private ChatRoom createRoomWithMembers( | ||
| String roomKey, | ||
| Long brandId, | ||
|
|
||
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.
The
ensureRoomAndGetIdmethod was changed to usecreateOrGetRoomSystem, which, as noted in the comments, bypasses authorization checks. This method is called by thehandleCampaignApplySentevent listener. The listener trusts thebrandUserIdandcreatorUserIdfrom theCampaignApplySentEventwithout verifying if the user who triggered the event is authorized to create a chat room for those users. This could allow an attacker who finds a way to publish a maliciousCampaignApplySentEventto create chat rooms between arbitrary users, leading to a breach of privacy. The previous implementation correctly passed the actor'suserIdto enforce authorization.