diff --git a/build.gradle b/build.gradle index b88f4542cc05422dcee3e5491eb9d93ed75f7ec7..1b29e4ade60f1ece03c8e7680004ea772cfc1ef3 100644 --- a/build.gradle +++ b/build.gradle @@ -30,12 +30,19 @@ dependencies { compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' runtimeOnly 'com.mysql:mysql-connector-j' - implementation 'org.pacesys:openstack4j:3.1.0' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "jakarta.annotation:jakarta.annotation-api" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + } tasks.named('test') { useJUnitPlatform() } +clean { + delete file('src/main/generated') +} \ No newline at end of file diff --git a/src/main/java/com/aolda/itda/aspect/ForwardingLogAspect.java b/src/main/java/com/aolda/itda/aspect/ForwardingLogAspect.java new file mode 100644 index 0000000000000000000000000000000000000000..d50c2feaaedc63a3b5dbac4fa28666ee146ca2a0 --- /dev/null +++ b/src/main/java/com/aolda/itda/aspect/ForwardingLogAspect.java @@ -0,0 +1,155 @@ +package com.aolda.itda.aspect; + +import com.aolda.itda.dto.forwarding.ForwardingDTO; +import com.aolda.itda.entity.forwarding.Forwarding; +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.forwarding.ForwardingRepository; +import com.aolda.itda.repository.log.LogRepository; +import com.aolda.itda.repository.routing.RoutingRepository; +import com.aolda.itda.repository.user.UserRepository; +import com.aolda.itda.service.AuthService; +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.transaction.annotation.Transactional; +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 ForwardingLogAspect { + + private final ForwardingRepository forwardingRepository; + private final LogRepository logRepository; + private final EntityManager entityManager; + private final UserRepository userRepository; + + /* Create 로깅 */ + @AfterReturning(pointcut = "execution(* com.aolda.itda.service.forwarding.*Service.*create*(..))" + , returning = "result") + public void createLogging(JoinPoint joinPoint, ForwardingDTO 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) + ); + + /* 생성된 엔티티 조회 */ + Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(result.getId(), false).orElse(null); + + /* 로그 메세지 작성 */ + String description = "name: " + forwarding.getName() + "\n" + + "serverPort: " + forwarding.getServerPort() + "\n" + + "instanceIp: " + forwarding.getInstanceIp() + "\n" + + "instancePort: " + forwarding.getInstancePort(); + + /* 로그 엔티티 저장 */ + logRepository.save(Log.builder() + .user(user) + .objectType(ObjectType.FORWARDING) + .objectId(forwarding.getForwardingId()) + .action(Action.CREATE) + .projectId(forwarding.getProjectId()) + .description(description) + .build()); + } + + /* Delete 로깅 */ + @AfterReturning(pointcut = "execution(* com.aolda.itda.service.forwarding.*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]; + Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(id, true).orElse(null); + + /* 로그 메세지 작성 */ + String description = "name: " + forwarding.getName() + "\n" + + "serverPort: " + forwarding.getServerPort() + "\n" + + "instanceIp: " + forwarding.getInstanceIp() + "\n" + + "instancePort: " + forwarding.getInstancePort(); + + /* 로그 엔티티 저장 */ + logRepository.save(Log.builder() + .user(user) + .objectType(ObjectType.FORWARDING) + .objectId(forwarding.getForwardingId()) + .action(Action.DELETE) + .projectId(forwarding.getProjectId()) + .description(description) + .build()); + } + + /* Update(edit) 로깅 */ + @Around("execution(* com.aolda.itda.service.forwarding.*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]; + Forwarding old = forwardingRepository.findByForwardingIdAndIsDeleted(id, false).orElse(null); + if (old != null) { + entityManager.detach(old); + } + + /* 메소드 진행 */ + Object result = joinPoint.proceed(); + + /* 변경 후 엔티티 조회*/ + Forwarding newObj = forwardingRepository.findByForwardingIdAndIsDeleted(id, false).orElse(null); + + /* 로그 메세지 작성 */ + String description = "name: " + old.getName() + (old.getName().equals(newObj.getName()) ? "" : (" -> " + newObj.getName())) + "\n" + + "serverPort: " + old.getServerPort() + (old.getServerPort().equals(newObj.getServerPort()) ? "" : (" -> " + newObj.getServerPort())) + "\n" + + "instanceIp: " + old.getInstanceIp() + (old.getInstanceIp().equals(newObj.getInstanceIp()) ? "" : (" -> " + newObj.getInstanceIp())) + "\n" + + "instancePort: " + old.getInstancePort() + (old.getInstancePort().equals(newObj.getInstancePort()) ? "" : (" -> " + newObj.getInstancePort())); + + /* 로그 엔티티 저장 */ + logRepository.save(Log.builder() + .user(user) + .objectType(ObjectType.FORWARDING) + .objectId(newObj.getForwardingId()) + .action(Action.UPDATE) + .projectId(newObj.getProjectId()) + .description(description) + .build()); + return result; + } + + + +} diff --git a/src/main/java/com/aolda/itda/aspect/RoutingLogAspect.java b/src/main/java/com/aolda/itda/aspect/RoutingLogAspect.java new file mode 100644 index 0000000000000000000000000000000000000000..a3a67d2dce9f65af87807d2c758eb3a225090b32 --- /dev/null +++ b/src/main/java/com/aolda/itda/aspect/RoutingLogAspect.java @@ -0,0 +1,170 @@ +package com.aolda.itda.aspect; + +import com.aolda.itda.dto.forwarding.ForwardingDTO; +import com.aolda.itda.dto.routing.RoutingDTO; +import com.aolda.itda.entity.forwarding.Forwarding; +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.routing.Routing; +import com.aolda.itda.entity.user.User; +import com.aolda.itda.exception.CustomException; +import com.aolda.itda.exception.ErrorCode; +import com.aolda.itda.repository.forwarding.ForwardingRepository; +import com.aolda.itda.repository.log.LogRepository; +import com.aolda.itda.repository.routing.RoutingRepository; +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.transaction.annotation.Transactional; +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 RoutingLogAspect { + + private final RoutingRepository routingRepository; + private final UserRepository userRepository; + private final LogRepository logRepository; + private final EntityManager entityManager; + + /* Create 로깅 */ + @AfterReturning(pointcut = "execution(* com.aolda.itda.service.routing.*Service.*create*(..))" + , returning = "result") + public void createLogging(JoinPoint joinPoint, RoutingDTO 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) + ); + + /* 생성된 엔티티 조회 */ + Routing routing = routingRepository.findByRoutingIdAndIsDeleted(result.getId(), false).orElse(null); + + /* 로그 메세지 작성 */ + String description = "name: " + routing.getName() + "\n" + + "domain: " + routing.getDomain() + "\n" + + "ip: " + routing.getInstanceIp() + "\n" + + "port: " + routing.getInstancePort() + "\n" + + (routing.getCertificate() != null ? ("certificateId: " + routing.getCertificate().getCertificateId() + "\n") : "") + + "caching: " + routing.getCaching() + "\n"; + + /* 로그 엔티티 저장 */ + logRepository.save(Log.builder() + .user(user) + .objectType(ObjectType.ROUTING) + .objectId(routing.getRoutingId()) + .action(Action.CREATE) + .projectId(routing.getProjectId()) + .description(description) + .build()); + } + + /* Delete 로깅 */ + @AfterReturning(pointcut = "execution(* com.aolda.itda.service.routing.*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]; + Routing routing = routingRepository.findByRoutingIdAndIsDeleted(id, true).orElse(null); + + /* 로그 메세지 작성 */ + String description = "name: " + routing.getName() + "\n" + + "domain: " + routing.getDomain() + "\n" + + "ip: " + routing.getInstanceIp() + "\n" + + "port: " + routing.getInstancePort() + "\n" + + (routing.getCertificate() != null ? ("certificateId: " + routing.getCertificate().getCertificateId() + "\n") : "") + + "caching: " + routing.getCaching() + "\n"; + + /* 로그 엔티티 저장 */ + logRepository.save(Log.builder() + .user(user) + .objectType(ObjectType.ROUTING) + .objectId(routing.getRoutingId()) + .action(Action.DELETE) + .projectId(routing.getProjectId()) + .description(description) + .build()); + } + + /* Update(edit) 로깅 */ + @Around("execution(* com.aolda.itda.service.routing.*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]; + Routing old = routingRepository.findByRoutingIdAndIsDeleted(id, false).orElse(null); + if (old != null) { + entityManager.detach(old); + } + + /* 메소드 진행 */ + Object result = joinPoint.proceed(); + + /* 변경 후 엔티티 조회 */ + Routing newObj = routingRepository.findByRoutingIdAndIsDeleted(id, false).orElse(null); + + /* 로그 메세지 작성 */ + String description = "name: " + old.getName() + (old.getName().equals(newObj.getName()) ? "" : " -> " + newObj.getName()) + "\n" + + "domain: " + old.getDomain() + (old.getDomain().equals(newObj.getDomain()) ? "" : " -> " + newObj.getDomain()) + "\n" + + "ip: " + old.getInstanceIp() + (old.getInstanceIp().equals(newObj.getInstanceIp()) ? "" : " -> " + newObj.getInstanceIp()) + "\n" + + "port: " + old.getInstancePort() + (old.getInstancePort().equals(newObj.getInstancePort()) ? "" : " -> " + newObj.getInstancePort()) + "\n"; + if (old.getCertificate() == null) { + if (newObj.getCertificate() != null) { + description = description + "certificateId: null -> " + newObj.getCertificate().getCertificateId() + "\n"; + } + } + else { + 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())); + + /* 로그 엔티티 저장 */ + logRepository.save(Log.builder() + .user(user) + .objectType(ObjectType.ROUTING) + .objectId(newObj.getRoutingId()) + .action(Action.UPDATE) + .projectId(newObj.getProjectId()) + .description(description) + .build()); + return result; + } +} diff --git a/src/main/java/com/aolda/itda/config/AuthInterceptor.java b/src/main/java/com/aolda/itda/config/AuthInterceptor.java index 63f2320e18fa92cd2edb275561be760518b19cc1..3a95f8a4e329d27457a75d18fac600f6fe84a836 100644 --- a/src/main/java/com/aolda/itda/config/AuthInterceptor.java +++ b/src/main/java/com/aolda/itda/config/AuthInterceptor.java @@ -1,5 +1,6 @@ package com.aolda.itda.config; +import com.aolda.itda.dto.auth.IdAndNameDTO; import com.aolda.itda.exception.CustomException; import com.aolda.itda.exception.ErrorCode; import com.aolda.itda.service.AuthService; @@ -10,6 +11,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; +import java.util.List; +import java.util.Map; + @RequiredArgsConstructor @Component @Slf4j @@ -20,18 +24,44 @@ public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("X-Subject-Token"); + + /* 토큰 헤더 검증 */ if (token == null || token.isEmpty()) { throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); } - // 어드민과 일반 유저 구분 필요 - try { - if (authService.validateTokenAndGetUserId(token) != null) { - return true; - } - } catch (Exception e) { - log.error("Token validation failed for URI {}: {}", request.getRequestURI(), e.getMessage(), e); + + /* 유효 토큰 검증 */ + String userId = authService.validateTokenAndGetUserId(token); + if (userId == null) { + log.error("Token validation failed for URI {}: {}", request.getRequestURI(), request.getRemoteAddr()); throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); } - throw new CustomException(ErrorCode.INVALID_TOKEN, request.getRequestURI()); + + /* 프로젝트 권한 검증 */ + String projectId = request.getParameter("projectId"); + if (projectId != null) { + + try { + String role = authService.getBestRoleWithinProject(token, projectId).get("role"); + if (!role.equals("admin")) { + log.error("Unauthorized Token for URI {}: {}", request.getRequestURI(), request.getRemoteAddr()); + throw new CustomException(ErrorCode.UNAUTHORIZED_USER, request.getRequestURI()); + } + } catch (Exception e) { + throw new CustomException(ErrorCode.UNAUTHORIZED_USER, request.getRequestURI()); + } + + + + } + + /* 프로젝트 리스트 조회 */ + List<String> projects = authService.getProjectsWithUser(Map.of("id", userId, "token", token)) + .stream().map(IdAndNameDTO::getId) + .toList(); + request.setAttribute("projects", projects); + request.setAttribute("user", Map.of("id", userId, "token", token)); + return true; + } } diff --git a/src/main/java/com/aolda/itda/config/WebConfig.java b/src/main/java/com/aolda/itda/config/WebConfig.java index 71e134bebee94ab01c47d0a032e6026d9929b62b..e3893aeecd8b181adc3bc069fbb7724fef9c38ab 100644 --- a/src/main/java/com/aolda/itda/config/WebConfig.java +++ b/src/main/java/com/aolda/itda/config/WebConfig.java @@ -1,6 +1,9 @@ package com.aolda.itda.config; +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; @@ -25,10 +28,15 @@ public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { - String[] excludeAuth = {"/error", "/api/auth/*", "/api/*" }; + String[] excludeAuth = {"/error", "/api/auth/*" }; registry.addInterceptor(authInterceptor) .addPathPatterns("/**") .excludePathPatterns(excludeAuth); } + + @Bean + public JPAQueryFactory jpaQueryFactory(EntityManager em) { + return new JPAQueryFactory(em); + } } diff --git a/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java b/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java index a6b67ecbd0090ce8f8460d3fd8b08def88d520a8..36e6f9a8138da9fee0f76d6028770260d9fb5679 100644 --- a/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java +++ b/src/main/java/com/aolda/itda/controller/forwarding/ForwardingController.java @@ -2,11 +2,14 @@ package com.aolda.itda.controller.forwarding; import com.aolda.itda.dto.forwarding.ForwardingDTO; import com.aolda.itda.service.forwarding.ForwardingService; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/api") @RequiredArgsConstructor @@ -22,8 +25,8 @@ public class ForwardingController { } @GetMapping("/forwarding") - public ResponseEntity<Object> view(@RequestParam Long forwardingId) { - return ResponseEntity.ok(forwardingService.getForwarding(forwardingId)); + public ResponseEntity<Object> view(@RequestParam Long forwardingId, HttpServletRequest request) { + return ResponseEntity.ok(forwardingService.getForwarding(forwardingId, (List<String>) request.getAttribute("projects"))); } @GetMapping("/forwardings") @@ -33,14 +36,16 @@ public class ForwardingController { @PatchMapping("/forwarding") public ResponseEntity<Object> edit(@RequestParam Long forwardingId, - @RequestBody ForwardingDTO dto) { - forwardingService.editForwarding(forwardingId, dto); + @RequestBody ForwardingDTO dto, + HttpServletRequest request) { + forwardingService.editForwarding(forwardingId, dto, (List<String>) request.getAttribute("projects") ); return ResponseEntity.ok().build(); } @DeleteMapping("/forwarding") - public ResponseEntity<Object> delete(@RequestParam Long forwardingId) { - forwardingService.deleteForwarding(forwardingId); + public ResponseEntity<Object> delete(@RequestParam Long forwardingId, + HttpServletRequest request) { + forwardingService.deleteForwarding(forwardingId, (List<String>) request.getAttribute("projects")); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/aolda/itda/controller/log/LogController.java b/src/main/java/com/aolda/itda/controller/log/LogController.java new file mode 100644 index 0000000000000000000000000000000000000000..0d4bfcf7bedf91d5fc7d1fbca6f4d68e6a5e48e8 --- /dev/null +++ b/src/main/java/com/aolda/itda/controller/log/LogController.java @@ -0,0 +1,41 @@ +package com.aolda.itda.controller.log; + +import com.aolda.itda.service.log.LogService; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.data.web.PageableDefault; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.Map; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class LogController { + + private final LogService logService; + + @GetMapping("/log") + public ResponseEntity<Object> view(@RequestParam Long logId, HttpServletRequest request) { + return ResponseEntity.ok(logService.getLog(logId, (List<String>) request.getAttribute("projects"))); + } + + @GetMapping("/logs") + public ResponseEntity<Object> lists(@RequestParam(required = false) String projectId + ,@RequestParam(required = false) String type + ,@RequestParam(required = false) String username + ,@RequestParam(required = false) String action + ,@RequestParam(defaultValue = "false") boolean isASC + ,@PageableDefault(size = 10) Pageable pageable + ,HttpServletRequest request) { + return ResponseEntity.ok(logService.getLogs(projectId, type, username, action, isASC, pageable, + (Map<String, String>) request.getAttribute("user"))); + } + +} diff --git a/src/main/java/com/aolda/itda/controller/routing/RoutingController.java b/src/main/java/com/aolda/itda/controller/routing/RoutingController.java index aec9927a0aad984e7d43cf3a750d7ef3d46207a2..4b9d551ada34e56464d8b95e1577adff3f456d36 100644 --- a/src/main/java/com/aolda/itda/controller/routing/RoutingController.java +++ b/src/main/java/com/aolda/itda/controller/routing/RoutingController.java @@ -3,10 +3,13 @@ package com.aolda.itda.controller.routing; import com.aolda.itda.dto.forwarding.ForwardingDTO; import com.aolda.itda.dto.routing.RoutingDTO; import com.aolda.itda.service.routing.RoutingService; +import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.List; + @RestController @RequestMapping("/api") @RequiredArgsConstructor @@ -14,7 +17,7 @@ public class RoutingController { private final RoutingService routingService; - @PostMapping("/forwarding") + @PostMapping("/routing") public ResponseEntity<Object> create(@RequestParam String projectId, @RequestBody RoutingDTO dto) { routingService.createRouting(projectId, dto); @@ -22,8 +25,9 @@ public class RoutingController { } @GetMapping("/routing") - public ResponseEntity<Object> view(@RequestParam Long routingId) { - return ResponseEntity.ok(routingService.getRouting(routingId)); + public ResponseEntity<Object> view(@RequestParam Long routingId, + HttpServletRequest request) { + return ResponseEntity.ok(routingService.getRouting(routingId, (List<String>) request.getAttribute("projects"))); } @GetMapping("/routings") @@ -33,14 +37,16 @@ public class RoutingController { @PatchMapping("/routing") public ResponseEntity<Object> edit(@RequestParam Long routingId, - @RequestBody RoutingDTO dto) { - routingService.editRouting(routingId, dto); + @RequestBody RoutingDTO dto, + HttpServletRequest request) { + routingService.editRouting(routingId, dto, (List<String>) request.getAttribute("projects")); return ResponseEntity.ok().build(); } @DeleteMapping("/routing") - public ResponseEntity<Object> delete(@RequestParam Long routingId) { - routingService.deleteRouting(routingId); + public ResponseEntity<Object> delete(@RequestParam Long routingId, + HttpServletRequest request) { + routingService.deleteRouting(routingId, (List<String>) request.getAttribute("projects")); return ResponseEntity.ok().build(); } diff --git a/src/main/java/com/aolda/itda/dto/PageResp.java b/src/main/java/com/aolda/itda/dto/PageResp.java index 49a2a6cdbacdee106e68588dc7b22cb43bb70fa7..7079600bbabe8b47e3e4c57f5267e8b287c8a473 100644 --- a/src/main/java/com/aolda/itda/dto/PageResp.java +++ b/src/main/java/com/aolda/itda/dto/PageResp.java @@ -15,7 +15,7 @@ import java.util.List; @JsonInclude(JsonInclude.Include.NON_NULL) public class PageResp<T> { private Integer totalPages; - private Integer totalElements; + private Long totalElements; private Integer size; private List<T> contents; private Boolean first; diff --git a/src/main/java/com/aolda/itda/dto/auth/IdAndNameDTO.java b/src/main/java/com/aolda/itda/dto/auth/IdAndNameDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..5918105381265b66446aa2d221657508e177fb58 --- /dev/null +++ b/src/main/java/com/aolda/itda/dto/auth/IdAndNameDTO.java @@ -0,0 +1,24 @@ +package com.aolda.itda.dto.auth; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.querydsl.core.annotations.QueryProjection; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class IdAndNameDTO { + + private String id; + private String name; + + @QueryProjection + public IdAndNameDTO(String id, String name) { + this.id = id; + this.name = name; + } +} diff --git a/src/main/java/com/aolda/itda/dto/auth/LoginResponseDTO.java b/src/main/java/com/aolda/itda/dto/auth/LoginResponseDTO.java index 33ba32fd358f5ab1fffecbe0965cc8921a88bbb2..a647b05835b9614cf21b1b8340a31b256fade9bc 100644 --- a/src/main/java/com/aolda/itda/dto/auth/LoginResponseDTO.java +++ b/src/main/java/com/aolda/itda/dto/auth/LoginResponseDTO.java @@ -13,5 +13,5 @@ import java.util.List; @Builder public class LoginResponseDTO { private Boolean isAdmin; - private List<ProjectIdAndNameDTO> projects; + private List<IdAndNameDTO> projects; } diff --git a/src/main/java/com/aolda/itda/dto/auth/ProjectIdAndNameDTO.java b/src/main/java/com/aolda/itda/dto/auth/ProjectIdAndNameDTO.java deleted file mode 100644 index 1ca894d2bf9b9cd5f29244ac95e7c4950daec174..0000000000000000000000000000000000000000 --- a/src/main/java/com/aolda/itda/dto/auth/ProjectIdAndNameDTO.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.aolda.itda.dto.auth; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Getter -@NoArgsConstructor -@AllArgsConstructor -@Builder -public class ProjectIdAndNameDTO { - - private String id; - private String name; -} diff --git a/src/main/java/com/aolda/itda/dto/log/LogDTO.java b/src/main/java/com/aolda/itda/dto/log/LogDTO.java new file mode 100644 index 0000000000000000000000000000000000000000..589545518431c85d093ca695b3cb97778fb9bac0 --- /dev/null +++ b/src/main/java/com/aolda/itda/dto/log/LogDTO.java @@ -0,0 +1,38 @@ +package com.aolda.itda.dto.log; + +import com.aolda.itda.dto.auth.IdAndNameDTO; +import com.aolda.itda.entity.log.Action; +import com.aolda.itda.entity.log.ObjectType; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.querydsl.core.annotations.QueryProjection; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Data +@NoArgsConstructor +@Builder +@JsonInclude(JsonInclude.Include.NON_NULL) +public class LogDTO { + private Long id; + private IdAndNameDTO user; + private Action action; + private ObjectType type; + private Long objectId; + private String description; + private LocalDateTime createdAt; + + @QueryProjection + public LogDTO(Long id, IdAndNameDTO user, Action action, ObjectType type, Long objectId, String description, LocalDateTime createdAt) { + this.id = id; + this.user = user; + this.action = action; + this.type = type; + this.objectId = objectId; + this.description = description; + this.createdAt = createdAt; + } +} diff --git a/src/main/java/com/aolda/itda/dto/routing/RoutingDTO.java b/src/main/java/com/aolda/itda/dto/routing/RoutingDTO.java index e95ab70e8763ab7c3db0a5e7d884d6e50662b1bf..6bc48d87f455151d743711bd40037496257fb940 100644 --- a/src/main/java/com/aolda/itda/dto/routing/RoutingDTO.java +++ b/src/main/java/com/aolda/itda/dto/routing/RoutingDTO.java @@ -25,7 +25,7 @@ public class RoutingDTO { @NotBlank private String port; private Long id; - @NotBlank + @NotNull private Long certificateId; private LocalDateTime createdAt; diff --git a/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java b/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java index 4460eb7b0c8c1401e10dcc56a34a09e81beca59a..6cfa1fde46f85bae5cf6c3ae7b2e543002db0ad9 100644 --- a/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java +++ b/src/main/java/com/aolda/itda/entity/BaseTimeEntity.java @@ -1,5 +1,6 @@ package com.aolda.itda.entity; +import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.MappedSuperclass; @@ -17,10 +18,12 @@ public abstract class BaseTimeEntity { @CreatedDate @Column(updatable = false) + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private LocalDateTime createdAt; @LastModifiedDate @Column(name = "updated_at") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul") private LocalDateTime updatedAt; } diff --git a/src/main/java/com/aolda/itda/entity/certificate/Certificate.java b/src/main/java/com/aolda/itda/entity/certificate/Certificate.java index 188f05e106121abe6f26b70fde078e0bd8d50662..a23cf858c8926a19b36a1b81df45b3b0bfd033f3 100644 --- a/src/main/java/com/aolda/itda/entity/certificate/Certificate.java +++ b/src/main/java/com/aolda/itda/entity/certificate/Certificate.java @@ -23,7 +23,7 @@ public class Certificate extends BaseTimeEntity { @Column(nullable = false) private Long certificateId; - @OneToOne + @ManyToOne @JoinColumn(nullable = false, name = "user_id") private User user; diff --git a/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java b/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java index b0c8d34715c26ce8334e6037ea97c65329761c72..0e5e50559860fbebf6cc5378fa91bd6fcefa00fa 100644 --- a/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java +++ b/src/main/java/com/aolda/itda/entity/forwarding/Forwarding.java @@ -4,10 +4,7 @@ import com.aolda.itda.dto.forwarding.ForwardingDTO; import com.aolda.itda.entity.BaseTimeEntity; import com.aolda.itda.entity.user.User; import jakarta.persistence.*; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; @Entity @Table(name = "forwarding") @@ -15,6 +12,7 @@ import lombok.NoArgsConstructor; @Builder @NoArgsConstructor @AllArgsConstructor +@ToString public class Forwarding extends BaseTimeEntity { @Id @@ -22,10 +20,6 @@ public class Forwarding extends BaseTimeEntity { @Column(nullable = false) private Long forwardingId; - @OneToOne - @JoinColumn(name = "user_id") - private User user; - private String projectId; private String serverIp; @@ -40,6 +34,17 @@ public class Forwarding extends BaseTimeEntity { private String name; + public Forwarding(Forwarding forwarding) { + this.forwardingId = forwarding.getForwardingId(); + this.projectId = forwarding.getProjectId(); + this.serverIp = forwarding.getServerIp(); + this.serverPort = forwarding.getServerPort(); + this.instanceIp = forwarding.getInstanceIp(); + this.instancePort = forwarding.getInstancePort(); + this.isDeleted = forwarding.getIsDeleted(); + this.name = forwarding.getName(); + } + public ForwardingDTO toForwardingDTO() { return ForwardingDTO.builder() .id(forwardingId) diff --git a/src/main/java/com/aolda/itda/entity/log/Log.java b/src/main/java/com/aolda/itda/entity/log/Log.java index fa78a6677f3cbb66322056a83dfd2a404eb5435c..9832ab291bdd2b8aaf5a8b6ea2d6823e71a55df7 100644 --- a/src/main/java/com/aolda/itda/entity/log/Log.java +++ b/src/main/java/com/aolda/itda/entity/log/Log.java @@ -1,5 +1,7 @@ package com.aolda.itda.entity.log; +import com.aolda.itda.dto.auth.IdAndNameDTO; +import com.aolda.itda.dto.log.LogDTO; import com.aolda.itda.entity.BaseTimeEntity; import com.aolda.itda.entity.user.User; import jakarta.persistence.*; @@ -21,7 +23,7 @@ public class Log extends BaseTimeEntity { @Column(nullable = false) private Long logId; - @OneToOne + @ManyToOne @JoinColumn(name = "user_id", nullable = false) private User user; @@ -37,4 +39,16 @@ public class Log extends BaseTimeEntity { private String description; + public LogDTO toLogDTO() { + return LogDTO.builder() + .id(logId) + .user(IdAndNameDTO.builder().id(user.getKeystoneId()).name(user.getKeystoneUsername()).build()) + .action(action) + .type(objectType) + .objectId(objectId) + .description(description) + .createdAt(getCreatedAt()) + .build(); + } + } diff --git a/src/main/java/com/aolda/itda/entity/routing/Routing.java b/src/main/java/com/aolda/itda/entity/routing/Routing.java index be03dcd3edd27000c8c00aea2437ac65da1c72d5..de13d82d149e0ec8c6acd60c162b9e980eb1bc91 100644 --- a/src/main/java/com/aolda/itda/entity/routing/Routing.java +++ b/src/main/java/com/aolda/itda/entity/routing/Routing.java @@ -24,12 +24,8 @@ public class Routing extends BaseTimeEntity { @Column(nullable = false) private Long routingId; - @OneToOne - @JoinColumn(name = "user_id", nullable = false) - private User user; - - @OneToOne - @JoinColumn(name = "certificate_id", nullable = false) + @ManyToOne + @JoinColumn(name = "certificate_id") private Certificate certificate; private String projectId; diff --git a/src/main/java/com/aolda/itda/exception/ErrorCode.java b/src/main/java/com/aolda/itda/exception/ErrorCode.java index 321bdaddd8b6eb5a3f523e8e56b5d738e3105319..3e4b0be8af87fa6ad85462e6a86fb52d6c295b2d 100644 --- a/src/main/java/com/aolda/itda/exception/ErrorCode.java +++ b/src/main/java/com/aolda/itda/exception/ErrorCode.java @@ -10,6 +10,8 @@ public enum ErrorCode { // User INVALID_USER_INFO(HttpStatus.BAD_REQUEST, "잘못된 회원 정보입니다"), + NOT_FOUND_USER(HttpStatus.BAD_REQUEST, "존재하지 않는 사용자입니다"), + UNAUTHORIZED_USER(HttpStatus.BAD_REQUEST, "권한이 없는 사용자입니다"), // Token INVALID_TOKEN(HttpStatus.BAD_REQUEST, "잘못된 토큰입니다"), @@ -19,6 +21,9 @@ public enum ErrorCode { // Routing NOT_FOUND_ROUTING(HttpStatus.BAD_REQUEST, "라우팅 파일이 존재하지 않습니다"), + + // Routing + NOT_FOUND_LOG(HttpStatus.BAD_REQUEST, "로그가 존재하지 않습니다"), // Certificate NOT_FOUND_CERTIFICATE(HttpStatus.BAD_REQUEST, "SSL 인증서가 존재하지 않습니다"), diff --git a/src/main/java/com/aolda/itda/repository/log/LogQueryDSL.java b/src/main/java/com/aolda/itda/repository/log/LogQueryDSL.java new file mode 100644 index 0000000000000000000000000000000000000000..ceba4b235799104d9b590a3d4c47aed6a92e11bc --- /dev/null +++ b/src/main/java/com/aolda/itda/repository/log/LogQueryDSL.java @@ -0,0 +1,102 @@ +package com.aolda.itda.repository.log; + +import com.aolda.itda.dto.PageResp; +import com.aolda.itda.dto.auth.QIdAndNameDTO; +import com.aolda.itda.dto.log.LogDTO; +import com.aolda.itda.dto.log.QLogDTO; +import com.aolda.itda.entity.log.Action; +import com.aolda.itda.entity.log.ObjectType; +import com.querydsl.core.BooleanBuilder; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; + +import java.util.List; + +import static com.aolda.itda.entity.log.QLog.*; +import static com.aolda.itda.entity.user.QUser.*; + +@Repository +@RequiredArgsConstructor +public class LogQueryDSL { + + private final JPAQueryFactory jpaQueryFactory; + + /* log 목록 반환 */ + public PageResp<LogDTO> getLogs(String projectId, String type, + String username, String action, Boolean isASC, Pageable pageable) { + + List<LogDTO> content = jpaQueryFactory + .select(new QLogDTO( + log.logId, + new QIdAndNameDTO(user.keystoneId, user.keystoneUsername), + log.action, + log.objectType, + log.objectId, + log.description, + log.createdAt + )).from(log) + .join(log.user, user).on(log.user.eq(user)) + .where(getFilter(projectId, type, username, action)) + .orderBy(isASC ? log.logId.asc() : log.logId.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery<Long> cnt = jpaQueryFactory + .select(log.count()) + .from(log) + .where(getFilter(projectId, type, username, action)); + + Page<LogDTO> page = PageableExecutionUtils.getPage(content, pageable, cnt::fetchOne); + + return PageResp.<LogDTO>builder() + .contents(page.getContent()) + .first(page.isFirst()) + .last(page.isLast()) + .size(page.getSize()) + .totalPages(page.getTotalPages()) + .totalElements(page.getTotalElements()) + .build(); + } + + /* Where 필터 */ + private BooleanBuilder getFilter(String projectId, String type, + String username, String action) { + BooleanBuilder builder = new BooleanBuilder(); + + /* 프로젝트 조건 */ + if (projectId != null) { + builder.and(log.projectId.eq(projectId)); + } + + /* 오브젝트 타입 조건 */ + if (type != null) { + switch (type) { + case "certificate" -> builder.and(log.objectType.eq(ObjectType.CERTIFICATE)); + case "forwarding" -> builder.and(log.objectType.eq(ObjectType.FORWARDING)); + case "routing" -> builder.and(log.objectType.eq(ObjectType.ROUTING)); + } + } + + + /* 사용자 ID 조건 */ + if (username != null) { + builder.and(log.user.keystoneUsername.eq(username)); + } + + /* CUD 조건 */ + if (action != null) { + switch (action) { + case "create" -> builder.and(log.action.eq(Action.CREATE)); + case "update" -> builder.and(log.action.eq(Action.UPDATE)); + case "delete" -> builder.and(log.action.eq(Action.DELETE)); + } + } + return builder; + } +} diff --git a/src/main/java/com/aolda/itda/repository/log/LogRepository.java b/src/main/java/com/aolda/itda/repository/log/LogRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..de0a769f6b1b206f5d350aae620fa997f7763006 --- /dev/null +++ b/src/main/java/com/aolda/itda/repository/log/LogRepository.java @@ -0,0 +1,10 @@ +package com.aolda.itda.repository.log; + +import com.aolda.itda.entity.log.Log; +import com.aolda.itda.entity.routing.Routing; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface LogRepository extends JpaRepository<Log, Long> { +} diff --git a/src/main/java/com/aolda/itda/repository/user/UserRepository.java b/src/main/java/com/aolda/itda/repository/user/UserRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..44cae688e4032b6caec7482c6c6f7e3c038736e2 --- /dev/null +++ b/src/main/java/com/aolda/itda/repository/user/UserRepository.java @@ -0,0 +1,11 @@ +package com.aolda.itda.repository.user; + +import com.aolda.itda.entity.user.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository<User, Long> { + Optional<User> findByKeystoneId(String keystoneId); + Optional<User> findByKeystoneUsername(String keystoneUsername); +} diff --git a/src/main/java/com/aolda/itda/service/AuthService.java b/src/main/java/com/aolda/itda/service/AuthService.java index cc523fa856bbb7101e995e049bb7b3bdfb196127..80c53d38568e4cd09d752bf2b8cbb11e788af23b 100644 --- a/src/main/java/com/aolda/itda/service/AuthService.java +++ b/src/main/java/com/aolda/itda/service/AuthService.java @@ -2,9 +2,11 @@ package com.aolda.itda.service; import com.aolda.itda.dto.auth.LoginRequestDTO; import com.aolda.itda.dto.auth.LoginResponseDTO; -import com.aolda.itda.dto.auth.ProjectIdAndNameDTO; +import com.aolda.itda.dto.auth.IdAndNameDTO; +import com.aolda.itda.entity.user.User; import com.aolda.itda.exception.CustomException; import com.aolda.itda.exception.ErrorCode; +import com.aolda.itda.repository.user.UserRepository; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -31,6 +33,7 @@ public class AuthService { private String adminPassword; private final RestTemplate restTemplate = new RestTemplate(); private final ObjectMapper objectMapper = new ObjectMapper(); + private final UserRepository userRepository; // 사용자 로그인 후 토큰 발행 및 Role 반환 public LoginResponseDTO userLogin(HttpServletResponse response, LoginRequestDTO loginRequestDTO) throws JsonProcessingException { @@ -44,6 +47,13 @@ public class AuthService { throw new CustomException(ErrorCode.INVALID_USER_INFO); } + + User entity = userRepository.findByKeystoneUsername(userId).orElse(null); + if (entity == null) { + userRepository.save(User.builder().keystoneId(validateTokenAndGetUserId(token)). + keystoneUsername(loginRequestDTO.getId()).build()); + } + response.addHeader("X-Subject-Token", systemToken != null ? systemToken : token); return LoginResponseDTO.builder() .isAdmin(systemToken != null) @@ -239,7 +249,7 @@ public class AuthService { } // 특정 사용자의 참여 프로젝트 반환 - private List<ProjectIdAndNameDTO> getProjectsWithUser(Map<String, String> user) throws JsonProcessingException { + public List<IdAndNameDTO> getProjectsWithUser(Map<String, String> user) throws JsonProcessingException { String userId = user.get("id"); String token = user.get("token"); if (userId == null || token == null) { @@ -257,12 +267,12 @@ public class AuthService { JsonNode node = objectMapper.readTree(res.getBody()); ArrayNode arrayNode = (ArrayNode) node.get("projects"); - List<ProjectIdAndNameDTO> lists = new ArrayList<>(); + List<IdAndNameDTO> lists = new ArrayList<>(); for (JsonNode assignment : arrayNode) { String projectId = assignment.path("id").asText(); String projectName = assignment.path("name").asText(); - lists.add(new ProjectIdAndNameDTO(projectId, projectName)); + lists.add(new IdAndNameDTO(projectId, projectName)); } return lists; } @@ -283,7 +293,13 @@ public class AuthService { } - private Boolean isAdmin(Map<String, String> user) throws JsonProcessingException { + public void validateProjectAuth(List<String> projects, String projectId) { + if (projects != null && !projects.contains(projectId)) { + throw new CustomException(ErrorCode.UNAUTHORIZED_USER); + } + } + + public Boolean isAdmin(Map<String, String> user) throws JsonProcessingException { String url = keystone + "/role_assignments?user.id=" + user.get("id") + "&scope.system&include_names"; HttpHeaders headers = new HttpHeaders(); headers.set("X-Auth-Token", user.get("token")); diff --git a/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java b/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java index 9ec934398f5635dbb05e868888506f2140757f1c..85c1ec3eb5450c7adae76f06f22724397f1e09f1 100644 --- a/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java +++ b/src/main/java/com/aolda/itda/service/forwarding/ForwardingService.java @@ -6,20 +6,16 @@ import com.aolda.itda.entity.forwarding.Forwarding; import com.aolda.itda.exception.CustomException; import com.aolda.itda.exception.ErrorCode; import com.aolda.itda.repository.forwarding.ForwardingRepository; +import com.aolda.itda.service.AuthService; import com.aolda.itda.template.ForwardingTemplate; -import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.HttpClientErrorException; import org.springframework.web.client.HttpServerErrorException; -import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import java.io.BufferedWriter; @@ -30,7 +26,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; -import java.util.Map; +import java.util.List; @Service @Transactional @@ -42,12 +38,17 @@ public class ForwardingService { private String serverBaseIp; private final ForwardingTemplate forwardingTemplate; private final ForwardingRepository forwardingRepository; + private final AuthService authService; private final RestTemplate restTemplate = new RestTemplate(); /* 포트포워딩 정보 조회 */ - public ForwardingDTO getForwarding(Long forwardingId) { + public ForwardingDTO getForwarding(Long forwardingId, List<String> projects) { Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(forwardingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, forwarding.getProjectId()); + return forwarding.toForwardingDTO(); } @@ -62,7 +63,7 @@ public class ForwardingService { } /* 포트포워딩 생성 */ - public void createForwarding(String projectId, ForwardingDTO dto) { + public ForwardingDTO createForwarding(String projectId, ForwardingDTO dto) { /* 입력 DTO 검증 */ validateDTO(dto); @@ -154,14 +155,16 @@ public class ForwardingService { } throw new CustomException(ErrorCode.FAIL_NGINX_CONF_RELOAD); } - + return forwarding.toForwardingDTO(); } /* 포트포워딩 정보 수정 */ - public void editForwarding(Long forwardingId, ForwardingDTO dto) { + public void editForwarding(Long forwardingId, ForwardingDTO dto, List<String> projects) { Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(forwardingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, forwarding.getProjectId()); /* 중복 검증 */ if (dto.getServerPort() != null && forwardingRepository.existsByServerPortAndIsDeleted(dto.getServerPort(), false)) { @@ -270,10 +273,13 @@ public class ForwardingService { } /* 포트포워딩 삭제 (소프트) */ - public void deleteForwarding(Long forwardingId) { + public void deleteForwarding(Long forwardingId, List<String> projects) { Forwarding forwarding = forwardingRepository.findByForwardingIdAndIsDeleted(forwardingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_FORWARDING)); + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, forwarding.getProjectId()); + /* 파일 삭제 */ String confPath = "/data/nginx/stream/" + forwarding.getForwardingId() + ".conf"; String deletePath = confPath + ".deleted"; diff --git a/src/main/java/com/aolda/itda/service/log/LogService.java b/src/main/java/com/aolda/itda/service/log/LogService.java new file mode 100644 index 0000000000000000000000000000000000000000..b33cc2e4a1e923ed046bea7aaff6ddc18c345c53 --- /dev/null +++ b/src/main/java/com/aolda/itda/service/log/LogService.java @@ -0,0 +1,57 @@ +package com.aolda.itda.service.log; + +import com.aolda.itda.dto.PageResp; +import com.aolda.itda.dto.log.LogDTO; +import com.aolda.itda.entity.log.Log; +import com.aolda.itda.exception.CustomException; +import com.aolda.itda.exception.ErrorCode; +import com.aolda.itda.repository.log.LogRepository; +import com.aolda.itda.repository.log.LogQueryDSL; +import com.aolda.itda.service.AuthService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.Map; + +@Service +@Transactional +@RequiredArgsConstructor +@Slf4j +public class LogService { + + private final LogRepository logRepository; + private final LogQueryDSL logQueryDSL; + private final AuthService authService; + + /* CUD 로그 조회 */ + public LogDTO getLog(Long logId, List<String> projects) { + Log log = logRepository.findById(logId) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_LOG)); + + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, log.getProjectId()); + + return log.toLogDTO(); + } + + /* CUD 로그 목록 조회 */ + public PageResp<LogDTO> getLogs(String projectId, String type, String username, String action, Boolean isASC, + Pageable pageable, Map<String, String> user) { + + if (projectId == null) { + try { + if(!authService.isAdmin(user)) throw new CustomException(ErrorCode.UNAUTHORIZED_USER); + } + catch (Exception e) { + throw new CustomException(ErrorCode.UNAUTHORIZED_USER); + } + } + + return logQueryDSL.getLogs(projectId, type, username, action, isASC, pageable); + } + +} diff --git a/src/main/java/com/aolda/itda/service/routing/RoutingService.java b/src/main/java/com/aolda/itda/service/routing/RoutingService.java index 9cfad7d4ec75d158fce65ced051f8036749ef5b2..7b30c9127796b6dda07d65207a0bc37abbdb9b92 100644 --- a/src/main/java/com/aolda/itda/service/routing/RoutingService.java +++ b/src/main/java/com/aolda/itda/service/routing/RoutingService.java @@ -8,6 +8,7 @@ import com.aolda.itda.exception.CustomException; import com.aolda.itda.exception.ErrorCode; import com.aolda.itda.repository.certificate.CertificateRepository; import com.aolda.itda.repository.routing.RoutingRepository; +import com.aolda.itda.service.AuthService; import com.aolda.itda.template.RoutingTemplate; import jakarta.validation.ConstraintViolation; import jakarta.validation.Validation; @@ -26,6 +27,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardCopyOption; +import java.util.List; @Service @Transactional @@ -35,20 +37,23 @@ public class RoutingService { private final RoutingRepository routingRepository; private final CertificateRepository certificateRepository; + private final AuthService authService; private final RoutingTemplate routingTemplate; private final RestTemplate restTemplate = new RestTemplate(); /* Routing 조회 */ - public RoutingDTO getRouting(Long routingId) { - // project id 확인 필요 + public RoutingDTO getRouting(Long routingId, List<String> projects) { Routing routing = routingRepository.findByRoutingIdAndIsDeleted(routingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROUTING)); + + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, routing.getProjectId()); + return routing.toRoutingDTO(); } /* Routing 목록 조회 */ public PageResp<RoutingDTO> getRoutings(String projectId) { - // project id 확인 필요 return PageResp.<RoutingDTO>builder() .contents(routingRepository.findByProjectIdAndIsDeleted(projectId, false) .stream() @@ -57,7 +62,7 @@ public class RoutingService { } /* Routing 생성 */ - public void createRouting(String projectId, RoutingDTO dto) { + public RoutingDTO createRouting(String projectId, RoutingDTO dto) { /* 입력 DTO 검증 */ validateDTO(dto); @@ -151,15 +156,16 @@ public class RoutingService { throw new CustomException(ErrorCode.FAIL_NGINX_CONF_RELOAD); } + return routing.toRoutingDTO(); } /* Routing 수정 */ - public void editRouting(Long routingId, RoutingDTO dto) { + public void editRouting(Long routingId, RoutingDTO dto, List<String> projects) { Routing routing = routingRepository.findByRoutingIdAndIsDeleted(routingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROUTING)); - /* 입력 DTO 검증 */ - validateDTO(dto); + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, routing.getProjectId()); /* 중복 검증 */ if (dto.getDomain() != null && routingRepository.existsByDomainAndIsDeleted(dto.getDomain(), false)) { @@ -173,7 +179,9 @@ public class RoutingService { /* 파일 수정 */ routing.edit(dto, certificate); - String content = routingTemplate.getRouting(routing.toRoutingDTO(), certificate == null ? null : certificate.formatDomain()); + RoutingDTO tmp = routing.toRoutingDTO(); + if (tmp.getCertificateId() == null) tmp.setCertificateId( (long) -1); + String content = routingTemplate.getRouting(tmp, certificate == null ? null : certificate.formatDomain()); String confPath = "/data/nginx/proxy_host/" + routing.getRoutingId() + ".conf"; File file = new File(confPath); if (!file.exists()) { @@ -253,10 +261,13 @@ public class RoutingService { } /* Routing 삭제 */ - public void deleteRouting(Long routingId) { + public void deleteRouting(Long routingId, List<String> projects) { Routing routing = routingRepository.findByRoutingIdAndIsDeleted(routingId, false) .orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ROUTING)); + /* 프로젝트 권한 검증 */ + authService.validateProjectAuth(projects, routing.getProjectId()); + /* 파일 삭제 */ String confPath = "/data/nginx/proxy_host/" + routing.getRoutingId() + ".conf"; String deletePath = confPath + ".deleted";