Skip to content
Snippets Groups Projects
Commit 55d6360c authored by 천 진강's avatar 천 진강
Browse files

feat: Certificate 사용자 활동 Log 추가

parent f9aae944
No related branches found
No related tags found
1 merge request!15Feat/certificate
Showing
with 222 additions and 29 deletions
package com.aolda.itda.aspect;
import com.aolda.itda.dto.certificate.CertificateDTO;
import com.aolda.itda.entity.certificate.Certificate;
import com.aolda.itda.entity.log.Action;
import com.aolda.itda.entity.log.Log;
import com.aolda.itda.entity.log.ObjectType;
import com.aolda.itda.entity.user.User;
import com.aolda.itda.exception.CustomException;
import com.aolda.itda.exception.ErrorCode;
import com.aolda.itda.repository.certificate.CertificateRepository;
import com.aolda.itda.repository.log.LogRepository;
import com.aolda.itda.repository.user.UserRepository;
import jakarta.persistence.EntityManager;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Map;
import java.util.Objects;
@Aspect
@Component
@RequiredArgsConstructor
public class CertificateLogAspect {
private final CertificateRepository certificateRepository;
private final UserRepository userRepository;
private final LogRepository logRepository;
private final EntityManager entityManager;
/* Create 로깅 */
@AfterReturning(pointcut = "execution(* com.aolda.itda.service.certificate.*Service.*create*(..))"
, returning = "result")
public void createLogging(JoinPoint joinPoint, CertificateDTO result) {
/* 사용자 조회 */
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
Map<String, String> tmp = (Map<String, String>) request.getAttribute("user");
User user = userRepository.findByKeystoneId(tmp.get("id")).orElseThrow(
() -> new CustomException(ErrorCode.NOT_FOUND_USER)
);
/* 생성된 엔티티 조회 */
Certificate certificate = certificateRepository.findByCertificateIdAndIsDeleted(result.getId(), false).orElse(null);
/* 로그 메세지 작성 */
String description = "domain: " + certificate.getDomain() + "\n"
+ "email: " + certificate.getEmail() + "\n"
+ "challenge: " + certificate.getChallenge() + "\n"
+ "expiresAt: " + certificate.getExpiresAt();
/* 로그 엔티티 저장 */
logRepository.save(Log.builder()
.user(user)
.objectType(ObjectType.CERTIFICATE)
.objectId(certificate.getCertificateId())
.action(Action.CREATE)
.projectId(certificate.getProjectId())
.description(description)
.build());
}
/* Delete 로깅 */
@AfterReturning(pointcut = "execution(* com.aolda.itda.service.certificate.*Service.*delete*(..))")
public void deleteLogging(JoinPoint joinPoint) {
/* 사용자 조회 */
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
Map<String, String> tmp = (Map<String, String>) request.getAttribute("user");
User user = userRepository.findByKeystoneId(tmp.get("id")).orElseThrow(
() -> new CustomException(ErrorCode.NOT_FOUND_USER)
);
/* 삭제된 엔티티 조회 */
Object[] args = joinPoint.getArgs();
Long id = (Long) args[0];
Certificate certificate = certificateRepository.findByCertificateIdAndIsDeleted(id, true).orElse(null);
/* 로그 메세지 작성 */
String description = "domain: " + certificate.getDomain() + "\n"
+ "email: " + certificate.getEmail() + "\n"
+ "challenge: " + certificate.getChallenge() + "\n"
+ "expiresAt: " + certificate.getExpiresAt();
/* 로그 엔티티 저장 */
logRepository.save(Log.builder()
.user(user)
.objectType(ObjectType.CERTIFICATE)
.objectId(certificate.getCertificateId())
.action(Action.DELETE)
.projectId(certificate.getProjectId())
.description(description)
.build());
}
/* Update(edit) 로깅 */
@Around("execution(* com.aolda.itda.service.certificate.*Service.*edit*(..))")
public Object editLogging(ProceedingJoinPoint joinPoint) throws Throwable {
/* 사용자 조회 */
HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
Map<String, String> tmp = (Map<String, String>) request.getAttribute("user");
User user = userRepository.findByKeystoneId(tmp.get("id")).orElseThrow(
() -> new CustomException(ErrorCode.NOT_FOUND_USER)
);
/* 변경 전 엔티티 조회 */
Object[] args = joinPoint.getArgs();
Long id = (Long) args[0];
Certificate old = certificateRepository.findByCertificateIdAndIsDeleted(id, false).orElse(null);
if (old != null) {
entityManager.detach(old);
}
/* 메소드 진행 */
Object result = joinPoint.proceed();
/* 변경 후 엔티티 조회 */
Certificate newObj = certificateRepository.findByCertificateIdAndIsDeleted(id, false).orElse(null);
/* 로그 메세지 작성 */
String description = "domain: " + old.getDomain() + (old.getDomain().equals(newObj.getDomain()) ? "" : " -> " + newObj.getDomain()) + "\n"
+ "email: " + old.getEmail() + (old.getEmail().equals(newObj.getEmail()) ? "" : " -> " + newObj.getEmail()) + "\n"
+ "challenge: " + old.getChallenge() + (old.getChallenge() == newObj.getChallenge() ? "" : " -> " + newObj.getChallenge()) + "\n"
+ "expiresAt: " + old.getExpiresAt() + (old.getExpiresAt().equals(newObj.getExpiresAt()) ? "" : " -> " + newObj.getExpiresAt());
/* 로그 엔티티 저장 */
logRepository.save(Log.builder()
.user(user)
.objectType(ObjectType.CERTIFICATE)
.objectId(newObj.getCertificateId())
.action(Action.UPDATE)
.projectId(newObj.getProjectId())
.description(description)
.build());
return result;
}
}
\ No newline at end of file
...@@ -141,19 +141,14 @@ public class RoutingLogAspect { ...@@ -141,19 +141,14 @@ public class RoutingLogAspect {
+ "domain: " + old.getDomain() + (old.getDomain().equals(newObj.getDomain()) ? "" : " -> " + newObj.getDomain()) + "\n" + "domain: " + old.getDomain() + (old.getDomain().equals(newObj.getDomain()) ? "" : " -> " + newObj.getDomain()) + "\n"
+ "ip: " + old.getInstanceIp() + (old.getInstanceIp().equals(newObj.getInstanceIp()) ? "" : " -> " + newObj.getInstanceIp()) + "\n" + "ip: " + old.getInstanceIp() + (old.getInstanceIp().equals(newObj.getInstanceIp()) ? "" : " -> " + newObj.getInstanceIp()) + "\n"
+ "port: " + old.getInstancePort() + (old.getInstancePort().equals(newObj.getInstancePort()) ? "" : " -> " + newObj.getInstancePort()) + "\n"; + "port: " + old.getInstancePort() + (old.getInstancePort().equals(newObj.getInstancePort()) ? "" : " -> " + newObj.getInstancePort()) + "\n";
if (old.getCertificate() == null) {
if (newObj.getCertificate() != null) { if (isSameCertificate(old, newObj)) {
description = description + "certificateId: null -> " + newObj.getCertificate().getCertificateId() + "\n"; description = description + "certificateId: " + (old.getCertificate() == null ? "null" : old.getCertificate().getCertificateId()) + "\n";
} } else {
} description = description + "certificateId: " + (old.getCertificate() == null ? "null" : old.getCertificate().getCertificateId()) +
else { (newObj.getCertificate() == null ? "null" : " -> " + newObj.getCertificate().getCertificateId()) + "\n";
if (newObj.getCertificate() == null) {
description = description + "certificateId: " + old.getCertificate().getCertificateId() + " -> null\n";
}
else {
description = description + "certificateId: " + old.getCertificate().getCertificateId() + " -> " + newObj.getCertificate().getCertificateId() + "\n";
}
} }
description = description + "caching: " + (old.getCaching() == newObj.getCaching() ? newObj.getCaching() : (" -> " + newObj.getCaching())); description = description + "caching: " + (old.getCaching() == newObj.getCaching() ? newObj.getCaching() : (" -> " + newObj.getCaching()));
/* 로그 엔티티 저장 */ /* 로그 엔티티 저장 */
...@@ -167,4 +162,14 @@ public class RoutingLogAspect { ...@@ -167,4 +162,14 @@ public class RoutingLogAspect {
.build()); .build());
return result; return result;
} }
private boolean isSameCertificate(Routing old, Routing newObj) {
if (old.getCertificate() == null && newObj.getCertificate() == null) {
return true;
} else if (old.getCertificate() == null || newObj.getCertificate() == null) {
return false;
} else {
return old.getCertificate().getCertificateId().equals(newObj.getCertificate().getCertificateId());
}
}
} }
...@@ -40,7 +40,7 @@ public class RoutingController { ...@@ -40,7 +40,7 @@ public class RoutingController {
@PatchMapping("/routing") @PatchMapping("/routing")
public ResponseEntity<Object> edit(@RequestParam Long routingId, public ResponseEntity<Object> edit(@RequestParam Long routingId,
@RequestBody RoutingDTO dto, @RequestBody RoutingDTO dto,
HttpServletRequest request) { HttpServletRequest request) throws IOException {
routingService.editRouting(routingId, dto, (List<String>) request.getAttribute("projects")); routingService.editRouting(routingId, dto, (List<String>) request.getAttribute("projects"));
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
......
package com.aolda.itda.dto.certificate; package com.aolda.itda.dto.certificate;
import com.aolda.itda.entity.certificate.Challenge; import com.aolda.itda.entity.certificate.Challenge;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.persistence.Column;
import lombok.*; import lombok.*;
import java.time.LocalDateTime; import java.time.LocalDateTime;
...@@ -16,9 +18,12 @@ public class CertificateDTO { ...@@ -16,9 +18,12 @@ public class CertificateDTO {
private Long id; // 인증서 고유 ID private Long id; // 인증서 고유 ID
// private String projectId; // 프로젝트 식별자 // private String projectId; // 프로젝트 식별자
private String domain; // SSL 인증받을 도메인 주소 private String domain; // SSL 인증받을 도메인 주소
private String email; // 도메인 소유자의 이메일 private String email;
private LocalDateTime expiresAt; // 인증서 만료일 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")// 도메인 소유자의 이메일
private LocalDateTime createdAt; // 인증서 생성일 private LocalDateTime expiresAt;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")// 인증서 만료일
private LocalDateTime createdAt;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")// 인증서 생성일
private LocalDateTime updatedAt; // 인증서 업데이트일 private LocalDateTime updatedAt; // 인증서 업데이트일
private Challenge challenge; // 챌린지 방식 (HTTP, DNS_CLOUDFLARE) private Challenge challenge; // 챌린지 방식 (HTTP, DNS_CLOUDFLARE)
private Boolean isDeleted; // 삭제 여부 (soft delete) private Boolean isDeleted; // 삭제 여부 (soft delete)
......
...@@ -2,6 +2,7 @@ package com.aolda.itda.entity.certificate; ...@@ -2,6 +2,7 @@ package com.aolda.itda.entity.certificate;
import com.aolda.itda.entity.BaseTimeEntity; import com.aolda.itda.entity.BaseTimeEntity;
import com.aolda.itda.entity.user.User; import com.aolda.itda.entity.user.User;
import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.persistence.*; import jakarta.persistence.*;
import lombok.*; import lombok.*;
...@@ -32,9 +33,8 @@ public class Certificate extends BaseTimeEntity { ...@@ -32,9 +33,8 @@ public class Certificate extends BaseTimeEntity {
private String email; private String email;
@Setter @Setter
@Column(columnDefinition = "DATETIME")
private LocalDateTime expiresAt; //인증서 만료일 private LocalDateTime expiresAt; //인증서 만료일
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private Challenge challenge; private Challenge challenge;
......
...@@ -69,7 +69,6 @@ public class Routing extends BaseTimeEntity { ...@@ -69,7 +69,6 @@ public class Routing extends BaseTimeEntity {
this.domain = dto.getDomain() != null ? dto.getDomain() : this.domain; this.domain = dto.getDomain() != null ? dto.getDomain() : this.domain;
this.certificate = certificate; this.certificate = certificate;
} }
public void delete() { public void delete() {
this.isDeleted = true; this.isDeleted = true;
} }
......
...@@ -14,4 +14,15 @@ public class ApiExceptionHandler { ...@@ -14,4 +14,15 @@ public class ApiExceptionHandler {
log.error("[handleCustomException] {} : {}, {}", e.getErrorCode().name(), e.getErrorCode().getMessage(), e.getStackTrace()); log.error("[handleCustomException] {} : {}, {}", e.getErrorCode().name(), e.getErrorCode().getMessage(), e.getStackTrace());
return ErrorResponse.fromException(e); return ErrorResponse.fromException(e);
} }
@ExceptionHandler(Exception.class)
public ResponseEntity<ErrorResponse> handleException(Exception e) {
log.error("Unexpected error occurred: {}", e.getMessage(), e);
ErrorResponse response = new ErrorResponse(
ErrorCode.INTERNAL_SERVER_ERROR.getStatus(),
ErrorCode.INTERNAL_SERVER_ERROR.getCode(),
ErrorCode.INTERNAL_SERVER_ERROR.getMessage()
);
return new ResponseEntity<>(response, ErrorCode.INTERNAL_SERVER_ERROR.getStatus());
}
} }
...@@ -43,8 +43,15 @@ public enum ErrorCode { ...@@ -43,8 +43,15 @@ public enum ErrorCode {
FAIL_DELETE_CONF(HttpStatus.BAD_REQUEST, "Conf 파일을 삭제하지 못했습니다"), FAIL_DELETE_CONF(HttpStatus.BAD_REQUEST, "Conf 파일을 삭제하지 못했습니다"),
FAIL_ROLL_BACK(HttpStatus.BAD_REQUEST, "롤백 실패"), FAIL_ROLL_BACK(HttpStatus.BAD_REQUEST, "롤백 실패"),
FAIL_CREATE_CERT(HttpStatus.BAD_REQUEST, "인증서 생성에 실패했습니다"); FAIL_CREATE_CERT(HttpStatus.BAD_REQUEST, "인증서 생성에 실패했습니다"),
// System
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "서버 내부 오류가 발생했습니다");
private final HttpStatus status; private final HttpStatus status;
private final String message; private final String message;
public String getCode() {
return this.name();
}
} }
package com.aolda.itda.exception; package com.aolda.itda.exception;
import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
import lombok.Getter; import lombok.Getter;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
...@@ -7,6 +8,7 @@ import org.springframework.http.ResponseEntity; ...@@ -7,6 +8,7 @@ import org.springframework.http.ResponseEntity;
@Getter @Getter
@Builder @Builder
@AllArgsConstructor
public class ErrorResponse { public class ErrorResponse {
private final HttpStatus status; // HTTP 상태 코드 private final HttpStatus status; // HTTP 상태 코드
......
package com.aolda.itda.repository.certificate; package com.aolda.itda.repository.certificate;
import com.aolda.itda.entity.certificate.Certificate; import com.aolda.itda.entity.certificate.Certificate;
import com.aolda.itda.entity.forwarding.Forwarding;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import java.time.LocalDateTime; import java.time.LocalDateTime;
...@@ -17,13 +16,13 @@ public interface CertificateRepository extends JpaRepository<Certificate, Long> ...@@ -17,13 +16,13 @@ public interface CertificateRepository extends JpaRepository<Certificate, Long>
List<Certificate> findByProjectIdAndIsDeleted(String projectId, Boolean isDeleted); List<Certificate> findByProjectIdAndIsDeleted(String projectId, Boolean isDeleted);
// 만료일이 주어진 날짜 이전인(=만료 30일 이내) 인증서 조회 // 만료일이 주어진 날짜 이전인(=만료 30일 이내) 인증서 조회
//List<Certificate> findByExpiredAtBeforeAndIsDeleted(LocalDateTime date, Boolean isDeleted); //List<Certificate> findByExpiresAtBeforeAndIsDeleted(LocalDateTime date, Boolean isDeleted);
// 1) domain 필터링용 메서드 // 1) domain 필터링용 메서드
List<Certificate> findByProjectIdAndDomainContainingIgnoreCaseAndIsDeleted( List<Certificate> findByProjectIdAndDomainContainingIgnoreCaseAndIsDeleted(
String projectId, String domain, Boolean isDeleted); String projectId, String domain, Boolean isDeleted);
// 3) 만료 30일 이내 대상 조회 // 3) 만료 30일 이내 대상 조회
List<Certificate> findByExpiredAtBeforeAndIsDeleted( List<Certificate> findByExpiresAtBeforeAndIsDeleted(
LocalDateTime date, Boolean isDeleted); LocalDateTime date, Boolean isDeleted);
} }
...@@ -125,7 +125,7 @@ public class CertificateService { ...@@ -125,7 +125,7 @@ public class CertificateService {
public void renewExpiringCertificates() { public void renewExpiringCertificates() {
LocalDateTime threshold = LocalDateTime.now().plusDays(30); LocalDateTime threshold = LocalDateTime.now().plusDays(30);
List<Certificate> expiring = certificateRepository List<Certificate> expiring = certificateRepository
.findByExpiredAtBeforeAndIsDeleted(threshold, false); .findByExpiresAtBeforeAndIsDeleted(threshold, false);
for (Certificate cert : expiring) { for (Certificate cert : expiring) {
try { try {
......
...@@ -174,7 +174,7 @@ public class RoutingService { ...@@ -174,7 +174,7 @@ public class RoutingService {
} }
/* Routing 수정 */ /* Routing 수정 */
public void editRouting(Long routingId, RoutingDTO dto, List<String> projects) { public void editRouting(Long routingId, RoutingDTO dto, List<String> projects) throws IOException {
Routing routing = routingRepository.findByRoutingIdAndIsDeleted(routingId, false) Routing routing = routingRepository.findByRoutingIdAndIsDeleted(routingId, false)
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROUTING)); .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROUTING));
...@@ -187,9 +187,16 @@ public class RoutingService { ...@@ -187,9 +187,16 @@ public class RoutingService {
} }
/* SSL 인증서 조회 */ /* SSL 인증서 조회 */
Certificate certificate = (dto.getCertificateId() == null) || (dto.getCertificateId() == -1 ) ? null : Certificate certificate;
certificateRepository.findById(dto.getCertificateId()) if (dto.getCertificateId() == null) {
certificate = routing.getCertificate();
}
else if (dto.getCertificateId() == -1) {
certificate = null;
} else {
certificate = certificateRepository.findById(dto.getCertificateId())
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_CERTIFICATE)); // isDeleted 확인 필요 .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_CERTIFICATE)); // isDeleted 확인 필요
}
/* 파일 수정 */ /* 파일 수정 */
routing.edit(dto, certificate); routing.edit(dto, certificate);
...@@ -220,7 +227,17 @@ public class RoutingService { ...@@ -220,7 +227,17 @@ public class RoutingService {
String url = "http://nginx:8081/nginx-api/test"; String url = "http://nginx:8081/nginx-api/test";
try { try {
restTemplate.getForEntity(url, String.class); restTemplate.getForEntity(url, String.class);
} catch (RuntimeException e) { } catch (HttpClientErrorException | HttpServerErrorException e) {
String responseBody = e.getResponseBodyAsString();
System.err.println("Response Body: " + responseBody);
Path filePath = Paths.get(confPath);
List<String> lines = Files.readAllLines(filePath);
// 파일 내용 출력
for (String line : lines) {
System.out.println(line);
}
try { try {
Files.copy(backup, Paths.get(confPath), StandardCopyOption.REPLACE_EXISTING Files.copy(backup, Paths.get(confPath), StandardCopyOption.REPLACE_EXISTING
, StandardCopyOption.COPY_ATTRIBUTES); , StandardCopyOption.COPY_ATTRIBUTES);
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment