Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.example.RealMatch.chat.application.event.apply;

import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
Expand All @@ -10,7 +8,7 @@
import org.springframework.transaction.event.TransactionalEventListener;

import com.example.RealMatch.business.application.event.CampaignApplySentEvent;
import com.example.RealMatch.chat.application.service.room.ChatRoomQueryService;
import com.example.RealMatch.chat.application.service.room.ChatRoomCommandService;
import com.example.RealMatch.chat.presentation.dto.response.ChatApplyCardPayloadResponse;

import lombok.RequiredArgsConstructor;
Expand All @@ -25,7 +23,7 @@ public class CampaignApplySentEventListener {

private static final Logger LOG = LoggerFactory.getLogger(CampaignApplySentEventListener.class);

private final ChatRoomQueryService chatRoomQueryService;
private final ChatRoomCommandService chatRoomCommandService;
private final ApplicationEventPublisher eventPublisher;

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
Expand All @@ -38,16 +36,8 @@ public void handleCampaignApplySent(CampaignApplySentEvent event) {
LOG.info("[ApplyBoundary] Received business event. applyId={}, campaignId={}, creatorUserId={}, brandUserId={}",
event.applyId(), event.campaignId(), event.creatorUserId(), event.brandUserId());

Optional<Long> roomIdOpt = chatRoomQueryService.getRoomIdByUserPair(
event.brandUserId(), event.creatorUserId());

if (roomIdOpt.isEmpty()) {
LOG.debug("[ApplyBoundary] Chat room not found. brandUserId={}, creatorUserId={}",
event.brandUserId(), event.creatorUserId());
return;
}

Long roomId = roomIdOpt.get();
// 채팅방이 없으면 생성
Long roomId = ensureRoomAndGetId(event.brandUserId(), event.creatorUserId());
ChatApplyCardPayloadResponse payload = createPayload(event);
String eventId = ApplySentEvent.generateEventId(event.applyId());

Expand All @@ -58,6 +48,16 @@ public void handleCampaignApplySent(CampaignApplySentEvent event) {
eventId, roomId, event.applyId());
}

/**
* 채팅방이 없으면 생성하고, roomId를 반환합니다.
* 이 리스너는 AFTER_COMMIT 컨텍스트에서 실행되므로 createOrGetRoom이 별도 트랜잭션으로 처리됩니다.
*/
private Long ensureRoomAndGetId(Long brandUserId, Long creatorUserId) {
return chatRoomCommandService
.createOrGetRoom(brandUserId, brandUserId, creatorUserId)
.roomId();
}
Comment on lines +55 to +59
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

This ensureRoomAndGetId method has a logical flaw leading to a Broken Access Control vulnerability. It incorrectly passes brandUserId as the actor's ID to createOrGetRoom. In the context of a CampaignApplySentEvent, the creatorUserId is the actual actor, meaning authorization checks are performed against the wrong user identity. This should be corrected to pass creatorUserId as the actor.

Suggested change
private Long ensureRoomAndGetId(Long brandUserId, Long creatorUserId) {
return chatRoomCommandService
.createOrGetRoom(brandUserId, brandUserId, creatorUserId)
.roomId();
}
private Long ensureRoomAndGetId(Long brandUserId, Long creatorUserId) {
return chatRoomCommandService
.createOrGetRoom(creatorUserId, brandUserId, creatorUserId)
.roomId();
}


private ChatApplyCardPayloadResponse createPayload(CampaignApplySentEvent event) {
return new ChatApplyCardPayloadResponse(
event.applyId(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.example.RealMatch.chat.application.event.proposal;

import java.util.Optional;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationEventPublisher;
Expand All @@ -12,7 +10,7 @@
import com.example.RealMatch.business.application.event.CampaignProposalSentEvent;
import com.example.RealMatch.business.domain.enums.ProposalDirection;
import com.example.RealMatch.business.domain.enums.ProposalStatus;
import com.example.RealMatch.chat.application.service.room.ChatRoomQueryService;
import com.example.RealMatch.chat.application.service.room.ChatRoomCommandService;
import com.example.RealMatch.chat.domain.enums.ChatProposalDirection;
import com.example.RealMatch.chat.presentation.dto.enums.ChatProposalDecisionStatus;
import com.example.RealMatch.chat.presentation.dto.response.ChatProposalCardPayloadResponse;
Expand All @@ -29,7 +27,7 @@ public class CampaignProposalSentEventListener {

private static final Logger LOG = LoggerFactory.getLogger(CampaignProposalSentEventListener.class);

private final ChatRoomQueryService chatRoomQueryService;
private final ChatRoomCommandService chatRoomCommandService;
private final ApplicationEventPublisher eventPublisher;

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
Expand All @@ -42,16 +40,8 @@ public void handleCampaignProposalSent(CampaignProposalSentEvent event) {
LOG.info("[ProposalBoundary] Received business event. proposalId={}, isReProposal={}",
event.proposalId(), event.isReProposal());

Optional<Long> roomIdOpt = chatRoomQueryService.getRoomIdByUserPair(
event.brandUserId(), event.creatorUserId());

if (roomIdOpt.isEmpty()) {
LOG.debug("[ProposalBoundary] Chat room not found. brandUserId={}, creatorUserId={}",
event.brandUserId(), event.creatorUserId());
return;
}

Long roomId = roomIdOpt.get();
// 채팅방이 없으면 생성
Long roomId = ensureRoomAndGetId(event.brandUserId(), event.creatorUserId());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

ensureRoomAndGetId 메소드가 올바른 행위자(actor)로 채팅방을 생성하려면 proposalDirection 정보가 필요합니다. event.proposalDirection()을 메소드에 전달해주세요.

Suggested change
Long roomId = ensureRoomAndGetId(event.brandUserId(), event.creatorUserId());
Long roomId = ensureRoomAndGetId(event.brandUserId(), event.creatorUserId(), event.proposalDirection());

ChatProposalCardPayloadResponse payload = createPayload(event);
String eventId = ProposalSentEvent.generateEventId(event.proposalId(), event.isReProposal());

Expand Down Expand Up @@ -88,6 +78,16 @@ private ChatProposalDecisionStatus toChatDecisionStatus(ProposalStatus proposalS
};
}

/**
* 채팅방이 없으면 생성하고, roomId를 반환합니다.
* 이 리스너는 AFTER_COMMIT 컨텍스트에서 실행되므로 createOrGetRoom이 별도 트랜잭션으로 처리됩니다.
*/
private Long ensureRoomAndGetId(Long brandUserId, Long creatorUserId) {
return chatRoomCommandService
.createOrGetRoom(brandUserId, brandUserId, creatorUserId)
.roomId();
}
Comment on lines +85 to +89
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-medium medium

This ensureRoomAndGetId method incorrectly hardcodes brandUserId as the actor for createOrGetRoom, leading to a Broken Access Control vulnerability. For a CampaignProposalSentEvent, the actor should be dynamically determined based on the proposalDirection. The current implementation performs authorization using the wrong user identity when the creator sends a proposal. The proposalDirection should be used to correctly identify the actor.

Suggested change
private Long ensureRoomAndGetId(Long brandUserId, Long creatorUserId) {
return chatRoomCommandService
.createOrGetRoom(brandUserId, brandUserId, creatorUserId)
.roomId();
}
private Long ensureRoomAndGetId(Long brandUserId, Long creatorUserId, ProposalDirection direction) {
Long actorId = ProposalDirection.CREATOR_TO_BRAND.equals(direction) ? creatorUserId : brandUserId;
return chatRoomCommandService
.createOrGetRoom(actorId, brandUserId, creatorUserId)
.roomId();
}


private ChatProposalDirection toChatProposalDirection(ProposalDirection direction) {
if (direction == null) {
return ChatProposalDirection.NONE;
Expand Down
Loading