Skip to content
Closed
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,12 +1,16 @@
package org.scoula.domain.contract.controller;

import javax.servlet.http.HttpServletResponse;

import org.scoula.domain.contract.dto.*;
import org.scoula.global.auth.dto.CustomUserDetails;
import org.scoula.global.common.dto.ApiResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.multipart.MultipartFile;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
Expand Down Expand Up @@ -114,4 +118,68 @@ ResponseEntity<ApiResponse<Void>> updateSpecialContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody SpecialContractUpdateDTO dto);

// =================

// 내보내기
// @ApiOperation(
// value = "[내보내기] 1 최종 계약서, 전자서명 테이블 초기 세팅",
// notes = "전자서명에 관련된 값들을 저장하기 위해 테이블 초기 세팅을 합니다.")
// ResponseEntity<ApiResponse<Void>> finalContractInit(
// @PathVariable Long contractChatId,
// @AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "[내보내기] 1 최종 계약서 PDF로 만들기 -> AI", notes = "최종 계약서에 들어갈 항목들로 최종 계약서 만들기 ")
ResponseEntity<ApiResponse<MultipartFile>> finalContractPDF(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails);

@ApiOperation(value = "[내보내기] 2 받아온 전자서명 파일 암호화 후 S3에 저장", notes = "전자서명 png를 s3에 저장합니다.")
ResponseEntity<ApiResponse<Boolean>> saveSignature(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestPart("dto") String dtoText, // JSON 파트
@RequestPart("imgFiles") MultipartFile imgFiles)
throws Exception;

@ApiOperation(value = "[내보내기] 3 최종 계약서 PDF S3에 저장", notes = "사용자에게 암호를 받아 암호화 후 S3에 저장하기")
ResponseEntity<ApiResponse<Void>> saveFinalContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody ContractPasswordDTO dto);

@ApiOperation(value = "[내보내기] 4 최종 계약서 PDF를 보여줍니다.", notes = "최종 계약서 PDF를 보여줍니다.")
ResponseEntity<ApiResponse<byte[]>> selectContractPDF(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails)
throws Exception;

// @ApiOperation(value = "최종 계약서 PDF 파일 받아와서 암호화 후 S3에 저장", notes = "최종 계약서 PDF를 암호화하여 S3에
// 저장합니다.")
// ResponseEntity<ApiResponse<Void>> saveContractPDF(
// @PathVariable Long contractChatId,
// @AuthenticationPrincipal CustomUserDetails userDetails,
// @RequestBody FinalContractDTO dto);
//
// @ApiOperation(value = "전자서명 다운로드", notes = "전자서명을 다운로드해서 복호화해서 프론트에 전송합니다.")
// ResponseEntity<ApiResponse<Void>> selectSignaturePDF(
// @PathVariable Long contractChatId,
// @AuthenticationPrincipal CustomUserDetails userDetails,
// HttpServletResponse response);

// 패스베리어블을 뭘로 받아올지 얘기해보기 : contract_id
@ApiOperation(value = "[내보내기] 5 계약서 PDF 파일 다운로드", notes = "계약서 PDF를 S3에서 꺼내 보내준다")
ResponseEntity<ApiResponse<Void>> selectContractPDF(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
HttpServletResponse response,
@RequestBody FindContractDTO dto)
throws Exception;

@ApiOperation(value = "[내보내기] 6 최종 계약서 PDF를 이메일로 전송", notes = "최종 계약서 PDF를 이메일로 전송합니다.")
ResponseEntity<ApiResponse<Void>> sendContractPDF(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody FindContractDTO dto)
throws Exception;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package org.scoula.domain.contract.controller;

import javax.servlet.http.HttpServletResponse;

import org.scoula.domain.contract.dto.*;
import org.scoula.domain.contract.service.ContractService;
import org.scoula.global.auth.dto.CustomUserDetails;
import org.scoula.global.common.dto.ApiResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import com.fasterxml.jackson.databind.ObjectMapper;

import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
Expand Down Expand Up @@ -191,4 +196,130 @@
return ResponseEntity.ok(
ApiResponse.success(service.sendStep4(contractChatId, userDetails.getUserId())));
}

@Override
@GetMapping("/finalContract")
public ResponseEntity<ApiResponse<byte[]>> selectContractPDF(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
return ResponseEntity.ok(
ApiResponse.success(
service.selectContractPDF(contractChatId, userDetails.getUserId())));
}

// ========================================

// @Override
// @PostMapping("/final_contract")
// public ResponseEntity<ApiResponse<Void>> finalContractInit(
// @PathVariable Long contractChatId,
// @AuthenticationPrincipal CustomUserDetails userDetails) {
// return ResponseEntity.ok(
// ApiResponse.success(
// service.finalContractInit(contractChatId,
// userDetails.getUserId())));
// }

@Override
@GetMapping("/final_contract")
public ResponseEntity<ApiResponse<MultipartFile>> finalContractPDF(

Check failure

Code scanning / CodeQL

HTTP request type unprotected from CSRF High

Potential CSRF vulnerability due to using an HTTP request type which is not default-protected from CSRF for an apparent
state-changing action
.
Potential CSRF vulnerability due to using an HTTP request type which is not default-protected from CSRF for an apparent
state-changing action
.

Copilot Autofix

AI 6 months ago

To fix this issue, the HTTP method for the finalContractPDF endpoint should be changed from GET to POST (or another "unsafe" method like PUT or PATCH) to ensure that Spring's default CSRF protection applies. This involves changing the @GetMapping("/final_contract") annotation to @PostMapping("/final_contract") on the finalContractPDF method in ContractControllerImpl.java. No changes are needed in the service layer, as the vulnerability is in the controller's HTTP method exposure. If clients are using this endpoint, they will need to update their requests to use POST instead of GET.

Steps:

  • In ContractControllerImpl.java, locate the finalContractPDF method.
  • Change @GetMapping("/final_contract") to @PostMapping("/final_contract").
  • No additional imports or method changes are required.

Suggested changeset 1
src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java b/src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java
--- a/src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java
+++ b/src/main/java/org/scoula/domain/contract/controller/ContractControllerImpl.java
@@ -221,7 +221,7 @@
       //      }
 
       @Override
-      @GetMapping("/final_contract")
+      @PostMapping("/final_contract")
       public ResponseEntity<ApiResponse<MultipartFile>> finalContractPDF(
               @PathVariable Long contractChatId,
               @AuthenticationPrincipal CustomUserDetails userDetails) {
EOF
@@ -221,7 +221,7 @@
// }

@Override
@GetMapping("/final_contract")
@PostMapping("/final_contract")
public ResponseEntity<ApiResponse<MultipartFile>> finalContractPDF(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
Copilot is powered by AI and may make mistakes. Always verify output.
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails) {
return ResponseEntity.ok(
ApiResponse.success(
service.finalContractPDF(contractChatId, userDetails.getUserId())));
}

@Override
@PostMapping("/signature/tax")
public ResponseEntity<ApiResponse<Boolean>> saveSignature(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
// @RequestPart("dto") SaveSignatureDTO dto, // JSON 파트
@RequestParam("dto") String dtoText,
@RequestPart("imgFiles") MultipartFile imgFiles)
throws Exception {
if (imgFiles == null || imgFiles.isEmpty()) {
throw new IllegalArgumentException("서명 이미지가 비어 있습니다.");
}
// 문자열 -> DTO (JSON도, 'TAX' 같은 단일 문자열도 허용)
SaveSignatureDTO dto;
try {
dto =
new ObjectMapper()
.readValue(dtoText, SaveSignatureDTO.class); // {"signedType":"TAX"}
} catch (Exception ignore) {
dto =
SaveSignatureDTO.builder()
.signedType(
org.scoula.domain.contract.enums.SignedType.valueOf(
dtoText.trim().toUpperCase()))
.build(); // TAX
}
return ResponseEntity.ok(
ApiResponse.success(
service.saveSignature(
contractChatId, userDetails.getUserId(), dto, imgFiles)));
}

@Override
@PostMapping("/finalContract/p")
public ResponseEntity<ApiResponse<Void>> saveFinalContract(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody ContractPasswordDTO dto) {
return ResponseEntity.ok(
ApiResponse.success(
service.saveFinalContract(contractChatId, userDetails.getUserId(), dto)));
}

// @Override
// @PostMapping("/pdf")
// public ResponseEntity<ApiResponse<Void>> saveContractPDF(
// @PathVariable Long contractChatId,
// @AuthenticationPrincipal CustomUserDetails userDetails,
// @RequestBody FinalContractDTO dto) {
// return ResponseEntity.ok(
// ApiResponse.success(
// service.saveContractPDF(contractChatId, userDetails.getUserId(),
// dto)));
// }
//
// @Override
// @GetMapping("/signature")
// public ResponseEntity<ApiResponse<Void>> selectSignaturePDF(
// @PathVariable Long contractChatId,
// @AuthenticationPrincipal CustomUserDetails userDetails,
// HttpServletResponse response) {
// return ResponseEntity.ok(
// ApiResponse.success(
// service.selectSignaturePDF(
// contractChatId, userDetails.getUserId(), response)));
// }

@Override
@PostMapping("/pdf")
public ResponseEntity<ApiResponse<Void>> selectContractPDF(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
HttpServletResponse response,
@RequestBody FindContractDTO dto)
throws Exception {
return ResponseEntity.ok(
ApiResponse.success(
service.selectContractPDF(
contractChatId, userDetails.getUserId(), response, dto)));
}

@Override
@PostMapping("/email")
public ResponseEntity<ApiResponse<Void>> sendContractPDF(
@PathVariable Long contractChatId,
@AuthenticationPrincipal CustomUserDetails userDetails,
@RequestBody FindContractDTO dto)
throws Exception {
return ResponseEntity.ok(
ApiResponse.success(
service.sendContractPDF(contractChatId, userDetails.getUserId(), dto)));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.scoula.domain.contract.dto;

import javax.validation.constraints.NotNull;

import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@ApiModel(description = "최종 계약서 비밀번호 받아오기")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ContractPasswordDTO {

@NotNull private String contractPassword;
// private String contractBuyerPassword;

@NotNull private Boolean mediationAgree; // 동의 여부
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.scoula.domain.contract.dto;

import java.math.BigDecimal;
import java.time.LocalDate;

import org.scoula.domain.precontract.enums.ContractDuration;

import io.swagger.annotations.ApiModel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@ApiModel(description = "DB에 있는 최종 계약서에 들어가는 내용들")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DBFinalContractDTO {

private String leaseType;
private String landCategory; // home detail _ 토지 지목
private BigDecimal area; // home detail _ 토지 면적

private String buildingStructure; // 건물 구조 _ '철근콘크리트 구조'로 고정하기
// private String purpose; // 성엽님 _ 건물 용도
// private float totalFloorArea; // 성엽님 _ 건물 면적

private boolean hasTaxArrears; // owner pre contract check _ 미납 국세, 지방세 여부
private boolean hasPriorFixedDate; // owner pre contract check _ 선순위 확정일자 현황

private int paymentDueDay; // owner_wolse_info _ 매월 지불 일자
private String bankAccount; // owner pre contract check _ 입금 계좌 & 은행 : owner_bank_name &
// owner_account_number

private LocalDate expectedMoveInDate; // tenant pre contract check 입주 날짜 -> === 특약사항에도 들어감 ===

private ContractDuration
contractDuration; // Tenant pre contract check에서 contract_duration으로 퇴거 날짜 계산해서 넣기
private LocalDate contractDate; // 계약하는 날짜 now()써서 하기

private String ownerSsnFront; // identity verification ssnFront + ssnBack 합쳐서 넣기
private String ownerSsnBack;
private String buyerSsnFront;
private String buyerSsnBack;
}
Loading
Loading