diff --git a/backend/src/main/java/com/park/utmstack/advice/GlobalExceptionHandler.java b/backend/src/main/java/com/park/utmstack/advice/GlobalExceptionHandler.java index 8f81d7326..937bdde86 100644 --- a/backend/src/main/java/com/park/utmstack/advice/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/park/utmstack/advice/GlobalExceptionHandler.java @@ -4,6 +4,8 @@ import com.park.utmstack.security.TooMuchLoginAttemptsException; import com.park.utmstack.service.application_events.ApplicationEventService; import com.park.utmstack.util.ResponseUtil; +import com.park.utmstack.util.exceptions.IncidentAlertConflictException; +import com.park.utmstack.util.exceptions.NoAlertsProvidedException; import com.park.utmstack.util.exceptions.TfaVerificationException; import com.park.utmstack.util.exceptions.TooManyRequestsException; import lombok.RequiredArgsConstructor; @@ -49,6 +51,16 @@ public ResponseEntity handleTooManyRequests(TooManyRequestsException e, HttpS return ResponseUtil.buildErrorResponse(HttpStatus.TOO_MANY_REQUESTS, e.getMessage()); } + @ExceptionHandler({NoAlertsProvidedException.class}) + public ResponseEntity handleNoAlertsProvided(Exception e, HttpServletRequest request) { + return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, e.getMessage()); + } + + @ExceptionHandler(IncidentAlertConflictException.class) + public ResponseEntity handleConflict(IncidentAlertConflictException e, HttpServletRequest request) { + return ResponseUtil.buildErrorResponse(HttpStatus.CONFLICT, e.getMessage()); + } + @ExceptionHandler(Exception.class) public ResponseEntity handleGenericException(Exception e, HttpServletRequest request) { return ResponseUtil.buildInternalServerErrorResponse(e.getMessage()); diff --git a/backend/src/main/java/com/park/utmstack/aop/logging/impl/AuditAspect.java b/backend/src/main/java/com/park/utmstack/aop/logging/impl/AuditAspect.java new file mode 100644 index 000000000..82cf76b58 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/aop/logging/impl/AuditAspect.java @@ -0,0 +1,82 @@ +package com.park.utmstack.aop.logging.impl; + +import com.park.utmstack.aop.logging.AuditEvent; +import com.park.utmstack.aop.logging.NoLogException; +import com.park.utmstack.domain.application_events.enums.ApplicationEventType; +import com.park.utmstack.loggin.LogContextBuilder; +import com.park.utmstack.service.application_events.ApplicationEventService; +import com.park.utmstack.service.dto.auditable.AuditableDTO; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import net.logstash.logback.argument.StructuredArguments; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.reflect.MethodSignature; +import org.slf4j.MDC; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +@Aspect +@Component +@Slf4j +@RequiredArgsConstructor +public class AuditAspect { + + private final ApplicationEventService applicationEventService; + private final LogContextBuilder logContextBuilder; + + @Around("@annotation(auditEvent)") + public Object logAuditEvent(ProceedingJoinPoint joinPoint, AuditEvent auditEvent) throws Throwable { + return handleAudit(joinPoint, auditEvent.attemptType(), auditEvent.successType(), + auditEvent.attemptMessage(), auditEvent.successMessage(), "controller"); + } + + private Object handleAudit(ProceedingJoinPoint joinPoint, + ApplicationEventType attemptType, + ApplicationEventType successType, + String attemptMessage, + String successMessage, + String layer) throws Throwable { + + MethodSignature signature = (MethodSignature) joinPoint.getSignature(); + String context = signature.getDeclaringType().getSimpleName() + "." + signature.getMethod().getName(); + MDC.put("context", context); + + Map extra = extractAuditData(joinPoint.getArgs()); + extra.put("layer", layer); + + try { + applicationEventService.createEvent(attemptMessage, attemptType, extra); + + Object result = joinPoint.proceed(); + + if (successType != ApplicationEventType.UNDEFINED) { + applicationEventService.createEvent(successMessage, successType, extra); + } + + return result; + + } catch (Exception e) { + if (!e.getClass().isAnnotationPresent(NoLogException.class)) { + String msg = String.format("%s: %s", context, e.getMessage()); + log.error(msg, e, StructuredArguments.keyValue("args", logContextBuilder.buildArgs(e))); + } + + throw e; + } + } + + private Map extractAuditData(Object[] args) { + Map extra = new HashMap<>(); + for (Object arg : args) { + if (arg instanceof AuditableDTO auditable) { + extra.putAll(auditable.toAuditMap()); + } + } + return extra; + } +} + diff --git a/backend/src/main/java/com/park/utmstack/aop/logging/impl/AuditEventAspect.java b/backend/src/main/java/com/park/utmstack/aop/logging/impl/AuditEventAspect.java deleted file mode 100644 index 8d822b98d..000000000 --- a/backend/src/main/java/com/park/utmstack/aop/logging/impl/AuditEventAspect.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.park.utmstack.aop.logging.impl; - -import com.park.utmstack.aop.logging.AuditEvent; -import com.park.utmstack.aop.utils.AuditContextExtractor; -import com.park.utmstack.domain.application_events.enums.ApplicationEventType; -import com.park.utmstack.loggin.LogContextBuilder; -import com.park.utmstack.service.application_events.ApplicationEventService; -import lombok.RequiredArgsConstructor; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.springframework.stereotype.Component; - -import java.util.List; -import java.util.Map; -import java.util.Objects; - -@Aspect -@Component -@RequiredArgsConstructor -public class AuditEventAspect { - - private final ApplicationEventService applicationEventService; - private final LogContextBuilder logContextBuilder; - private final List extractors; - - @Around("@annotation(auditEvent)") - public Object logAuditEvent(ProceedingJoinPoint joinPoint, AuditEvent auditEvent) throws Throwable { - Map args = logContextBuilder.buildArgs(); - for (AuditContextExtractor extractor : extractors) { - args.putAll(extractor.extract(joinPoint)); - } - - applicationEventService.createEvent(auditEvent.attemptMessage(), auditEvent.attemptType(), args); - - Object result = joinPoint.proceed(); - - if (!auditEvent.successType().equals(ApplicationEventType.UNDEFINED)) { - applicationEventService.createEvent(auditEvent.successMessage(), auditEvent.successType(), args); - } - - return result; - } - -} - diff --git a/backend/src/main/java/com/park/utmstack/aop/logging/impl/ControllerTracingAspect.java b/backend/src/main/java/com/park/utmstack/aop/logging/impl/ControllerTracingAspect.java deleted file mode 100644 index 71c18e452..000000000 --- a/backend/src/main/java/com/park/utmstack/aop/logging/impl/ControllerTracingAspect.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.park.utmstack.aop.logging.impl; - -import com.park.utmstack.aop.logging.NoLogException; -import com.park.utmstack.loggin.LogContextBuilder; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import net.logstash.logback.argument.StructuredArguments; -import org.aspectj.lang.ProceedingJoinPoint; -import org.aspectj.lang.annotation.Around; -import org.aspectj.lang.annotation.Aspect; -import org.aspectj.lang.reflect.MethodSignature; -import org.slf4j.MDC; -import org.springframework.stereotype.Component; - -@Aspect -@Slf4j -@Component -@RequiredArgsConstructor -public class ControllerTracingAspect { - - private final LogContextBuilder logContextBuilder; - - @Around("within(@org.springframework.web.bind.annotation.RestController *)") - public Object enrichMDC(ProceedingJoinPoint joinPoint) throws Throwable { - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - String context = signature.getDeclaringType().getSimpleName() + "." + signature.getMethod().getName(); - MDC.put("context", context); - try { - return joinPoint.proceed(); - } catch (Exception e) { - if (!e.getClass().isAnnotationPresent(NoLogException.class)) { - String msg = String.format("%s: %s", context, e.getMessage()); - log.error(msg, e, StructuredArguments.keyValue("args", logContextBuilder.buildArgs(e))); - } - throw e; - } - } -} - diff --git a/backend/src/main/java/com/park/utmstack/aop/utils/AuditContextExtractor.java b/backend/src/main/java/com/park/utmstack/aop/utils/AuditContextExtractor.java deleted file mode 100644 index 24a052eef..000000000 --- a/backend/src/main/java/com/park/utmstack/aop/utils/AuditContextExtractor.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.park.utmstack.aop.utils; - -import org.aspectj.lang.ProceedingJoinPoint; - -import java.util.Map; - -public interface AuditContextExtractor { - Map extract(ProceedingJoinPoint joinPoint); -} - diff --git a/backend/src/main/java/com/park/utmstack/aop/utils/impl/JwtLoginAuditContextExtractor.java b/backend/src/main/java/com/park/utmstack/aop/utils/impl/JwtLoginAuditContextExtractor.java deleted file mode 100644 index 5de6d4d08..000000000 --- a/backend/src/main/java/com/park/utmstack/aop/utils/impl/JwtLoginAuditContextExtractor.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.park.utmstack.aop.utils.impl; - -import com.park.utmstack.aop.utils.AuditContextExtractor; -import com.park.utmstack.web.rest.vm.LoginVM; -import org.aspectj.lang.ProceedingJoinPoint; -import org.springframework.stereotype.Component; - -import java.util.HashMap; -import java.util.Map; - -@Component -public class JwtLoginAuditContextExtractor implements AuditContextExtractor { - - @Override - public Map extract(ProceedingJoinPoint joinPoint) { - Map context = new HashMap<>(); - - for (Object arg : joinPoint.getArgs()) { - if (arg instanceof LoginVM loginVM) { - context.put("loginAttempt", loginVM.getUsername()); - } - } - - return context; - } -} - diff --git a/backend/src/main/java/com/park/utmstack/config/Constants.java b/backend/src/main/java/com/park/utmstack/config/Constants.java index 8757f1260..b1f771850 100644 --- a/backend/src/main/java/com/park/utmstack/config/Constants.java +++ b/backend/src/main/java/com/park/utmstack/config/Constants.java @@ -36,8 +36,8 @@ public final class Constants { public static final String PROP_NETWORK_SCAN_API_URL = "utmstack.networkScan.apiUrl"; public static final String PROP_TFA_ENABLE = "utmstack.tfa.enable"; public static final String PROP_TFA_METHOD = "utmstack.tfa.method"; - public static final int EXPIRES_IN_SECONDS = 30; - public static final int INIT_EXPIRES_IN_SECONDS = 300; + public static final int EXPIRES_IN_SECONDS_TOTP = 30; // Google Authenticator + public static final int EXPIRES_IN_SECONDS_EMAIL = 120; // Email OTP public static final String TFA_ISSUER = "UTMStack"; // ---------------------------------------------------------------------------------- diff --git a/backend/src/main/java/com/park/utmstack/config/LoggingConfiguration.java b/backend/src/main/java/com/park/utmstack/config/LoggingConfiguration.java index 75b9a14d6..03a9aa9cb 100644 --- a/backend/src/main/java/com/park/utmstack/config/LoggingConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/LoggingConfiguration.java @@ -3,8 +3,11 @@ import ch.qos.logback.classic.LoggerContext; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; +import com.park.utmstack.loggin.filter.MdcCleanupFilter; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import tech.jhipster.config.JHipsterProperties; @@ -45,4 +48,13 @@ public LoggingConfiguration( addContextListener(context, customFields, loggingProperties); } } + + @Bean + public FilterRegistrationBean mdcCleanupFilter() { + FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new MdcCleanupFilter()); + registrationBean.setOrder(Integer.MAX_VALUE); + registrationBean.addUrlPatterns("/*"); + return registrationBean; + } } diff --git a/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java b/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java index ef197e6e5..9fe019ace 100644 --- a/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java +++ b/backend/src/main/java/com/park/utmstack/config/SecurityConfiguration.java @@ -75,15 +75,6 @@ public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - @Bean - public FilterRegistrationBean mdcCleanupFilter() { - FilterRegistrationBean registrationBean = new FilterRegistrationBean<>(); - registrationBean.setFilter(new MdcCleanupFilter()); - registrationBean.setOrder(Integer.MAX_VALUE); - registrationBean.addUrlPatterns("/*"); - return registrationBean; - } - @Bean public WebSecurityCustomizer webSecurityCustomizer() { return web -> web.ignoring() @@ -119,7 +110,10 @@ public void configure(HttpSecurity http) throws Exception { .antMatchers("/api/account/reset-password/init").permitAll() .antMatchers("/api/account/reset-password/finish").permitAll() .antMatchers("/api/images/all").permitAll() - .antMatchers("/api/tfa/**").hasAnyAuthority(AuthoritiesConstants.PRE_VERIFICATION_USER, AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER) + .antMatchers("/api/enrollment/**").hasAnyAuthority(AuthoritiesConstants.PRE_VERIFICATION_USER) + .antMatchers("/api/tfa/verify-code").hasAnyAuthority(AuthoritiesConstants.PRE_VERIFICATION_USER, AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN) + .antMatchers("/api/tfa/refresh").hasAnyAuthority(AuthoritiesConstants.PRE_VERIFICATION_USER, AuthoritiesConstants.USER, AuthoritiesConstants.ADMIN) + .antMatchers("/api/tfa/**").hasAnyAuthority(AuthoritiesConstants.ADMIN, AuthoritiesConstants.USER) .antMatchers("/api/utm-incident-jobs").hasAuthority(AuthoritiesConstants.ADMIN) .antMatchers("/api/utm-incident-jobs/**").hasAuthority(AuthoritiesConstants.ADMIN) .antMatchers("/api/utm-incident-variables/**").hasAuthority(AuthoritiesConstants.ADMIN) diff --git a/backend/src/main/java/com/park/utmstack/domain/application_events/enums/ApplicationEventType.java b/backend/src/main/java/com/park/utmstack/domain/application_events/enums/ApplicationEventType.java index 8e6397939..91c5c8a19 100644 --- a/backend/src/main/java/com/park/utmstack/domain/application_events/enums/ApplicationEventType.java +++ b/backend/src/main/java/com/park/utmstack/domain/application_events/enums/ApplicationEventType.java @@ -31,6 +31,14 @@ public enum ApplicationEventType { CONFIG_GROUP_BULK_DELETE_SUCCESS, CONFIG_UPDATE_ATTEMPT, CONFIG_UPDATE_SUCCESS, + INCIDENT_CREATION_ATTEMPT, + INCIDENT_CREATED, + INCIDENT_CREATION_SUCCESS, + INCIDENT_ALERTS_ADDED, + INCIDENT_ALERT_ADD_ATTEMPT, + INCIDENT_ALERT_ADD_SUCCESS, + INCIDENT_UPDATE_ATTEMPT, + INCIDENT_UPDATE_SUCCESS, ERROR, WARNING, INFO, diff --git a/backend/src/main/java/com/park/utmstack/domain/tfa/TfaStage.java b/backend/src/main/java/com/park/utmstack/domain/tfa/TfaStage.java new file mode 100644 index 000000000..1bd87893f --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/domain/tfa/TfaStage.java @@ -0,0 +1,7 @@ +package com.park.utmstack.domain.tfa; + +public enum TfaStage { + INIT, + VERIFY, + COMPLETE +} diff --git a/backend/src/main/java/com/park/utmstack/service/UserService.java b/backend/src/main/java/com/park/utmstack/service/UserService.java index 578548dd7..ed1a6379b 100644 --- a/backend/src/main/java/com/park/utmstack/service/UserService.java +++ b/backend/src/main/java/com/park/utmstack/service/UserService.java @@ -218,6 +218,8 @@ public void updateUserTfaSecret(String userLogin, String tfaSecret, String tfaMe .orElseThrow(() -> new NoSuchElementException(String.format("User %1$s not found", userLogin))); user.setTfaMethod(tfaMethod); user.setTfaSecret(tfaSecret); + + userRepository.save(user); } public void deleteUser(String login) { diff --git a/backend/src/main/java/com/park/utmstack/service/UtmConfigurationParameterService.java b/backend/src/main/java/com/park/utmstack/service/UtmConfigurationParameterService.java index c90e5e6c1..48ceb04ff 100644 --- a/backend/src/main/java/com/park/utmstack/service/UtmConfigurationParameterService.java +++ b/backend/src/main/java/com/park/utmstack/service/UtmConfigurationParameterService.java @@ -162,13 +162,13 @@ public Map getValueMapForDateSetting() throws Exception { } } - public List getConfigParameterBySectionId(long sectionId) throws Exception { + public List getConfigParameterBySectionId(long sectionId) { final String ctx = CLASSNAME + ".getConfigParameterBySectionId"; try { return new ArrayList<>(configParamRepository .findAllBySectionId(sectionId)); } catch (Exception e) { - throw new Exception(ctx + ": " + e.getMessage()); + throw new RuntimeException(ctx + ": " + e.getMessage()); } } diff --git a/backend/src/main/java/com/park/utmstack/service/dto/auditable/AuditableDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/auditable/AuditableDTO.java new file mode 100644 index 000000000..60f04c0bd --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/dto/auditable/AuditableDTO.java @@ -0,0 +1,7 @@ +package com.park.utmstack.service.dto.auditable; + +import java.util.Map; + +public interface AuditableDTO { + Map toAuditMap(); +} diff --git a/backend/src/main/java/com/park/utmstack/service/dto/incident/AddToIncidentDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/incident/AddToIncidentDTO.java index 2e648f75d..154e58da1 100644 --- a/backend/src/main/java/com/park/utmstack/service/dto/incident/AddToIncidentDTO.java +++ b/backend/src/main/java/com/park/utmstack/service/dto/incident/AddToIncidentDTO.java @@ -1,9 +1,17 @@ package com.park.utmstack.service.dto.incident; +import com.park.utmstack.service.dto.auditable.AuditableDTO; +import lombok.Getter; +import lombok.Setter; + import javax.validation.constraints.NotNull; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; -public class AddToIncidentDTO { +@Setter +@Getter +public class AddToIncidentDTO implements AuditableDTO { @NotNull public Long incidentId; @NotNull @@ -12,19 +20,15 @@ public class AddToIncidentDTO { public AddToIncidentDTO() { } - public Long getIncidentId() { - return incidentId; - } - - public void setIncidentId(Long incidentId) { - this.incidentId = incidentId; - } - - public List getAlertList() { - return alertList; - } + @Override + public Map toAuditMap() { + List alertIds = alertList.stream() + .map(RelatedIncidentAlertsDTO::getAlertId) + .toList(); - public void setAlertList(List alertList) { - this.alertList = alertList; + return Map.of( + "incidentId", incidentId, + "alertIds", alertIds + ); } } diff --git a/backend/src/main/java/com/park/utmstack/service/dto/incident/NewIncidentDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/incident/NewIncidentDTO.java index 7b4c4b9ef..50d11ed7e 100644 --- a/backend/src/main/java/com/park/utmstack/service/dto/incident/NewIncidentDTO.java +++ b/backend/src/main/java/com/park/utmstack/service/dto/incident/NewIncidentDTO.java @@ -1,10 +1,17 @@ package com.park.utmstack.service.dto.incident; +import com.park.utmstack.service.dto.auditable.AuditableDTO; +import lombok.Getter; +import lombok.Setter; + import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; import java.util.List; +import java.util.Map; -public class NewIncidentDTO { +@Setter +@Getter +public class NewIncidentDTO implements AuditableDTO { @NotNull @Pattern(regexp = "^[^\"]*$", message = "Double quotes are not allowed") public String incidentName; @@ -16,36 +23,16 @@ public class NewIncidentDTO { public NewIncidentDTO() { } - public String getIncidentName() { - return incidentName; - } - - public void setIncidentName(String incidentName) { - this.incidentName = incidentName; - } - - public String getIncidentDescription() { - return incidentDescription; - } - - public void setIncidentDescription(String incidentDescription) { - this.incidentDescription = incidentDescription; - } - - public String getIncidentAssignedTo() { - return incidentAssignedTo; - } - - public void setIncidentAssignedTo(String incidentAssignedTo) { - this.incidentAssignedTo = incidentAssignedTo; - } - - public List getAlertList() { - return alertList; - } + @Override + public Map toAuditMap() { + List alertIds = alertList.stream() + .map(RelatedIncidentAlertsDTO::getAlertId) + .toList(); - public void setAlertList(List alertList) { - this.alertList = alertList; + return Map.of( + "incidentName", incidentName, + "alertIds", alertIds + ); } @Deprecated diff --git a/backend/src/main/java/com/park/utmstack/service/dto/jwt/LoginResponseDTO.java b/backend/src/main/java/com/park/utmstack/service/dto/jwt/LoginResponseDTO.java index 69572ee49..c1a6594c2 100644 --- a/backend/src/main/java/com/park/utmstack/service/dto/jwt/LoginResponseDTO.java +++ b/backend/src/main/java/com/park/utmstack/service/dto/jwt/LoginResponseDTO.java @@ -13,5 +13,6 @@ public class LoginResponseDTO { private boolean forceTfa; private String method; private String token; + private long tfaExpiresInSeconds; } diff --git a/backend/src/main/java/com/park/utmstack/service/dto/tfa/enroll/TfaEnrollRequest.java b/backend/src/main/java/com/park/utmstack/service/dto/tfa/enroll/TfaEnrollRequest.java new file mode 100644 index 000000000..b331422c3 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/service/dto/tfa/enroll/TfaEnrollRequest.java @@ -0,0 +1,19 @@ +package com.park.utmstack.service.dto.tfa.enroll; + +import com.park.utmstack.domain.tfa.TfaMethod; +import com.park.utmstack.domain.tfa.TfaStage; +import com.park.utmstack.service.dto.tfa.save.TfaSaveRequest; +import com.park.utmstack.service.dto.tfa.verify.TfaVerifyRequest; +import lombok.Data; + +@Data +public class TfaEnrollRequest { + private TfaStage stage; + private TfaMethod method; + private String code; + private boolean enable; + + public TfaVerifyRequest toVerifyRequest() { + return new TfaVerifyRequest(method, code); + } +} diff --git a/backend/src/main/java/com/park/utmstack/service/impl/UtmAlertServiceImpl.java b/backend/src/main/java/com/park/utmstack/service/impl/UtmAlertServiceImpl.java index ed80a37f3..c3ef3f13a 100644 --- a/backend/src/main/java/com/park/utmstack/service/impl/UtmAlertServiceImpl.java +++ b/backend/src/main/java/com/park/utmstack/service/impl/UtmAlertServiceImpl.java @@ -169,7 +169,8 @@ public void updateStatus(List alertIds, int status, String statusObserva String alertsIds = String.join(",", alertIds); Map extra = Map.of( "alertIds", alertsIds, - "newStatus", status + "newStatus", status, + "source", "service" ); String attemptMsg = String.format("Attempt to update status to %1$s for alerts with ids: %2$s", diff --git a/backend/src/main/java/com/park/utmstack/service/incident/UtmIncidentService.java b/backend/src/main/java/com/park/utmstack/service/incident/UtmIncidentService.java index b56146a8a..f5dc323b6 100644 --- a/backend/src/main/java/com/park/utmstack/service/incident/UtmIncidentService.java +++ b/backend/src/main/java/com/park/utmstack/service/incident/UtmIncidentService.java @@ -17,6 +17,7 @@ import com.park.utmstack.service.dto.incident.NewIncidentDTO; import com.park.utmstack.service.dto.incident.RelatedIncidentAlertsDTO; import com.park.utmstack.service.incident.util.ResolveIncidentStatus; +import com.park.utmstack.util.exceptions.IncidentAlertConflictException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.data.domain.Page; @@ -27,10 +28,7 @@ import org.springframework.util.CollectionUtils; import javax.validation.Valid; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.stream.Collectors; /** @@ -76,7 +74,6 @@ public UtmIncidentService(UtmIncidentRepository utmIncidentRepository, */ public UtmIncident save(UtmIncident utmIncident) { final String ctx = ".save"; - log.debug("Request to save UtmIncident : {}", utmIncident); try { return utmIncidentRepository.save(utmIncident); } catch (Exception e) { @@ -140,6 +137,8 @@ public UtmIncident changeStatus(UtmIncident utmIncident) { public UtmIncident createIncident(NewIncidentDTO newIncidentDTO) { final String ctx = CLASSNAME + ".createIncident"; try { + validateAlertsNotAlreadyLinked(newIncidentDTO.getAlertList(), ctx); + UtmIncident utmIncident = new UtmIncident(); utmIncident.setIncidentName(newIncidentDTO.getIncidentName()); utmIncident.setIncidentDescription(newIncidentDTO.getIncidentDescription()); @@ -177,10 +176,25 @@ public UtmIncident addAlertsIncident(@Valid AddToIncidentDTO addToIncidentDTO) { final String ctx = CLASSNAME + ".addAlertsIncident"; try { log.debug("Request to add alert to UtmIncident : {}", addToIncidentDTO); + + List alertIds = addToIncidentDTO.getAlertList(); + + String alertsIds = alertIds.stream().map(RelatedIncidentAlertsDTO::getAlertId).collect(Collectors.joining(",")); + Map extra = Map.of( + "alertIds", alertsIds, + "source", "service" + ); + String attemptMsg = String.format("Attempt to add %d alerts to incident %d", addToIncidentDTO.getAlertList().size(), addToIncidentDTO.getIncidentId()); + eventService.createEvent(attemptMsg, ApplicationEventType.INCIDENT_ALERT_ADD_ATTEMPT, extra); + + validateAlertsNotAlreadyLinked(addToIncidentDTO.getAlertList(), ctx); UtmIncident utmIncident = utmIncidentRepository.findById(addToIncidentDTO.getIncidentId()).orElseThrow(() -> new RuntimeException(ctx + ": Incident not found")); saveRelatedAlerts(addToIncidentDTO.getAlertList(), utmIncident.getId()); String historyMessage = String.format("New %d alerts added to incident", addToIncidentDTO.getAlertList().size()); utmIncidentHistoryService.createHistory(IncidentHistoryActionEnum.INCIDENT_ALERT_ADD, utmIncident.getId(), "New alerts added to incident", historyMessage); + + eventService.createEvent(historyMessage, ApplicationEventType.INCIDENT_ALERTS_ADDED, extra); + return utmIncident; } catch (Exception e) { String msg = ctx + ": " + e.getMessage(); @@ -284,4 +298,20 @@ private void sendIncidentsEmail(List alertIds, UtmIncident utmIncident) eventService.createEvent(msg, ApplicationEventType.ERROR); } } + + private void validateAlertsNotAlreadyLinked(List alertList, String ctx) { + + List alertIds = alertList.stream() + .map(RelatedIncidentAlertsDTO::getAlertId) + .collect(Collectors.toList()); + + List alertsFound = utmIncidentAlertService.existsAnyAlert(alertIds); + + if (!alertsFound.isEmpty()) { + String alertIdsList = String.join(", ", alertIds); + String msg = "Some alerts are already linked to another incident. Alert IDs: " + alertIdsList + ". Check the related incidents for more details."; + + throw new IncidentAlertConflictException(ctx + ": " + msg); + } + } } diff --git a/backend/src/main/java/com/park/utmstack/service/tfa/EmailTfaService.java b/backend/src/main/java/com/park/utmstack/service/tfa/EmailTfaService.java index 18d106da1..6136ff00c 100644 --- a/backend/src/main/java/com/park/utmstack/service/tfa/EmailTfaService.java +++ b/backend/src/main/java/com/park/utmstack/service/tfa/EmailTfaService.java @@ -92,7 +92,7 @@ public void generateChallenge(User user) { String secret = user.getTfaSecret(); String code = tfaService.generateCode(secret); - TfaSetupState state = new TfaSetupState(secret, System.currentTimeMillis() + Constants.EXPIRES_IN_SECONDS * 1000 * 10); + TfaSetupState state = new TfaSetupState(secret, System.currentTimeMillis() + (Constants.EXPIRES_IN_SECONDS_EMAIL * 4) * 1000 * 10); cache.storeState(user.getLogin(), TfaMethod.EMAIL, state); mailService.sendTfaVerificationCode(user, code); @@ -109,12 +109,17 @@ public void regenerateChallenge(User user) { throw new TooManyRequestsException("Challenge request too soon. Please wait " + state.getCooldownRemainingSeconds() + " seconds."); } - state.setExpiresAt(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(Constants.EXPIRES_IN_SECONDS)); + state.setExpiresAt(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(Constants.EXPIRES_IN_SECONDS_EMAIL)); state.markChallengeRequested(); mailService.sendTfaVerificationCode(user, tfaService.generateCode(state.getSecret())); cache.storeState(user.getLogin(), TfaMethod.EMAIL, state); } + + @Override + public long expirationTimeSeconds() { + return Constants.EXPIRES_IN_SECONDS_EMAIL; + } } diff --git a/backend/src/main/java/com/park/utmstack/service/tfa/EmailTotpService.java b/backend/src/main/java/com/park/utmstack/service/tfa/EmailTotpService.java index fa1abd795..1e4b358a5 100644 --- a/backend/src/main/java/com/park/utmstack/service/tfa/EmailTotpService.java +++ b/backend/src/main/java/com/park/utmstack/service/tfa/EmailTotpService.java @@ -9,7 +9,7 @@ import org.springframework.stereotype.Service; import org.springframework.util.Assert; -import static com.park.utmstack.config.Constants.EXPIRES_IN_SECONDS; +import static com.park.utmstack.config.Constants.EXPIRES_IN_SECONDS_EMAIL; @Service public class EmailTotpService { @@ -38,7 +38,7 @@ public String generateCode(String secret) { try { Assert.hasText(secret, "Secret value is missing"); CodeGenerator codeGenerator = new DefaultCodeGenerator(); - return codeGenerator.generate(secret, Math.floorDiv(timeProvider.getTime(), EXPIRES_IN_SECONDS)); + return codeGenerator.generate(secret, Math.floorDiv(timeProvider.getTime(), EXPIRES_IN_SECONDS_EMAIL)); } catch (Exception e) { throw new RuntimeException(ctx + ": " + e.getMessage()); } @@ -57,7 +57,7 @@ public boolean validateCode(String secret, String code) { Assert.hasText(code, "Code value is missing"); DefaultCodeVerifier verifier = new DefaultCodeVerifier(new DefaultCodeGenerator(), timeProvider); - verifier.setTimePeriod(EXPIRES_IN_SECONDS); + verifier.setTimePeriod(EXPIRES_IN_SECONDS_EMAIL); verifier.setAllowedTimePeriodDiscrepancy(1); return verifier.isValidCode(secret, code); diff --git a/backend/src/main/java/com/park/utmstack/service/tfa/TfaMethodService.java b/backend/src/main/java/com/park/utmstack/service/tfa/TfaMethodService.java index 1a55398ee..d93ed9eeb 100644 --- a/backend/src/main/java/com/park/utmstack/service/tfa/TfaMethodService.java +++ b/backend/src/main/java/com/park/utmstack/service/tfa/TfaMethodService.java @@ -12,11 +12,13 @@ public interface TfaMethodService { TfaVerifyResponse verifyCode(User use, String code); - void persistConfiguration(User use) throws Exception; + void persistConfiguration(User use); void generateChallenge(User user); void regenerateChallenge(User user); + long expirationTimeSeconds(); + } diff --git a/backend/src/main/java/com/park/utmstack/service/tfa/TfaService.java b/backend/src/main/java/com/park/utmstack/service/tfa/TfaService.java index b11ea69c2..8fd6868f0 100644 --- a/backend/src/main/java/com/park/utmstack/service/tfa/TfaService.java +++ b/backend/src/main/java/com/park/utmstack/service/tfa/TfaService.java @@ -36,18 +36,20 @@ public TfaVerifyResponse verifyCode(User user, TfaVerifyRequest request) { return selected.verifyCode(user, request.getCode()); } - public void persistConfiguration(TfaMethod method) throws Exception { + public void persistConfiguration(TfaMethod method) { User user = userService.getCurrentUserLogin(); TfaMethodService selected = getMethodService(method); selected.persistConfiguration(user); } - public void generateChallenge(User user) { + public long generateChallenge(User user) { TfaMethod method = TfaMethod.valueOf(user.getTfaMethod()); TfaMethodService selected = getMethodService(method); selected.generateChallenge(user); + + return selected.expirationTimeSeconds(); } public void regenerateChallenge(User user) { diff --git a/backend/src/main/java/com/park/utmstack/service/tfa/TotpTfaService.java b/backend/src/main/java/com/park/utmstack/service/tfa/TotpTfaService.java index 8c21a2c22..06f458b7f 100644 --- a/backend/src/main/java/com/park/utmstack/service/tfa/TotpTfaService.java +++ b/backend/src/main/java/com/park/utmstack/service/tfa/TotpTfaService.java @@ -47,7 +47,7 @@ public TfaMethod getMethod() { @Loggable public TfaInitResponse initiateSetup(User user) { String secret = authenticator.createCredentials().getKey(); - long expiresAt = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(Constants.EXPIRES_IN_SECONDS); + long expiresAt = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(Constants.EXPIRES_IN_SECONDS_TOTP * 10); TfaSetupState state = new TfaSetupState(secret, expiresAt); cache.storeState(user.getLogin(), TfaMethod.TOTP, state); @@ -57,7 +57,7 @@ public TfaInitResponse initiateSetup(User user) { String qrBase64 = generateQrBase64(uri); Delivery delivery = new Delivery(TfaMethod.TOTP, qrBase64); - return new TfaInitResponse("pending", delivery, Constants.EXPIRES_IN_SECONDS * 10); + return new TfaInitResponse("pending", delivery, Constants.EXPIRES_IN_SECONDS_TOTP * 10); } @Override @@ -92,7 +92,7 @@ public void persistConfiguration(User user) { public void generateChallenge(User user) { cache.clear(user.getLogin(), TfaMethod.TOTP); String secret = user.getTfaSecret(); - TfaSetupState state = new TfaSetupState(secret, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(Constants.EXPIRES_IN_SECONDS)); + TfaSetupState state = new TfaSetupState(secret, System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(Constants.EXPIRES_IN_SECONDS_TOTP)); cache.storeState(user.getLogin(), TfaMethod.TOTP, state); } @@ -107,7 +107,7 @@ public void regenerateChallenge(User user) { throw new TooManyRequestsException("Challenge request too soon. Please wait " + state.getCooldownRemainingSeconds() + " seconds."); } - state.setExpiresAt(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(Constants.EXPIRES_IN_SECONDS)); + state.setExpiresAt(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(Constants.EXPIRES_IN_SECONDS_TOTP)); state.markChallengeRequested(); cache.storeState(user.getLogin(), TfaMethod.TOTP, state); @@ -126,6 +126,11 @@ private String generateQrBase64(String uri) { } } + @Override + public long expirationTimeSeconds() { + return Constants.EXPIRES_IN_SECONDS_TOTP; + } + } diff --git a/backend/src/main/java/com/park/utmstack/util/exceptions/IncidentAlertConflictException.java b/backend/src/main/java/com/park/utmstack/util/exceptions/IncidentAlertConflictException.java new file mode 100644 index 000000000..c5aa47586 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/util/exceptions/IncidentAlertConflictException.java @@ -0,0 +1,7 @@ +package com.park.utmstack.util.exceptions; + +public class IncidentAlertConflictException extends RuntimeException { + public IncidentAlertConflictException(String message) { + super(message); + } +} diff --git a/backend/src/main/java/com/park/utmstack/util/exceptions/InvalidTfaStageException.java b/backend/src/main/java/com/park/utmstack/util/exceptions/InvalidTfaStageException.java new file mode 100644 index 000000000..207554ca9 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/util/exceptions/InvalidTfaStageException.java @@ -0,0 +1,8 @@ +package com.park.utmstack.util.exceptions; + +public class InvalidTfaStageException extends RuntimeException { + public InvalidTfaStageException(String message) { + super(message); + } +} + diff --git a/backend/src/main/java/com/park/utmstack/util/exceptions/NoAlertsProvidedException.java b/backend/src/main/java/com/park/utmstack/util/exceptions/NoAlertsProvidedException.java new file mode 100644 index 000000000..9f8c7b22e --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/util/exceptions/NoAlertsProvidedException.java @@ -0,0 +1,7 @@ +package com.park.utmstack.util.exceptions; + +public class NoAlertsProvidedException extends RuntimeException { + public NoAlertsProvidedException(String message) { + super(message); + } +} diff --git a/backend/src/main/java/com/park/utmstack/web/rest/UserJWTController.java b/backend/src/main/java/com/park/utmstack/web/rest/UserJWTController.java index 4e90eec61..0744540ef 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/UserJWTController.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/UserJWTController.java @@ -94,9 +94,10 @@ public ResponseEntity authorize(@Valid @RequestBody LoginVM lo boolean isTfaSetup = isTfaEnabled && user.getTfaMethod() != null && !user.getTfaMethod().isEmpty() && !isAuth; Map args = logContextBuilder.buildArgs(request); + Long tfaExpiresInSeconds = 0L; if (isTfaSetup) { - tfaService.generateChallenge(user); + tfaExpiresInSeconds = tfaService.generateChallenge(user); args.put("tfaMethod", user.getTfaMethod()); applicationEventService.createEvent( @@ -118,6 +119,7 @@ public ResponseEntity authorize(@Valid @RequestBody LoginVM lo .success(true) .tfaConfigured(isTfaSetup) .forceTfa(!isAuth) + .tfaExpiresInSeconds(tfaExpiresInSeconds) .build(); return ResponseEntity.ok(response); diff --git a/backend/src/main/java/com/park/utmstack/web/rest/incident/UtmIncidentResource.java b/backend/src/main/java/com/park/utmstack/web/rest/incident/UtmIncidentResource.java index bb3126878..f7b02d189 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/incident/UtmIncidentResource.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/incident/UtmIncidentResource.java @@ -2,59 +2,44 @@ import com.park.utmstack.domain.application_events.enums.ApplicationEventType; import com.park.utmstack.domain.incident.UtmIncident; -import com.park.utmstack.service.application_events.ApplicationEventService; import com.park.utmstack.service.dto.incident.*; -import com.park.utmstack.service.incident.UtmIncidentAlertService; import com.park.utmstack.service.incident.UtmIncidentQueryService; import com.park.utmstack.service.incident.UtmIncidentService; -import com.park.utmstack.util.ResponseUtil; +import com.park.utmstack.util.exceptions.NoAlertsProvidedException; import com.park.utmstack.web.rest.errors.BadRequestAlertException; import com.park.utmstack.web.rest.util.HeaderUtil; import com.park.utmstack.web.rest.util.PaginationUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.CollectionUtils; import org.springframework.web.bind.annotation.*; +import com.park.utmstack.aop.logging.AuditEvent; import javax.validation.Valid; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; /** * REST controller for managing UtmIncident. */ @RestController +@RequiredArgsConstructor +@Slf4j @RequestMapping("/api") public class UtmIncidentResource { - private final String CLASS_NAME = "UtmIncidentResource"; - private final Logger log = LoggerFactory.getLogger(UtmIncidentResource.class); private static final String ENTITY_NAME = "utmIncident"; private final UtmIncidentService utmIncidentService; - private final UtmIncidentAlertService utmIncidentAlertService; - private final UtmIncidentQueryService utmIncidentQueryService; - private final ApplicationEventService applicationEventService; - - public UtmIncidentResource(UtmIncidentService utmIncidentService, - UtmIncidentAlertService utmIncidentAlertService, - UtmIncidentQueryService utmIncidentQueryService, - ApplicationEventService applicationEventService) { - this.utmIncidentService = utmIncidentService; - this.utmIncidentAlertService = utmIncidentAlertService; - this.utmIncidentQueryService = utmIncidentQueryService; - this.applicationEventService = applicationEventService; - } + /** * Creates a new incident based on the provided details. @@ -74,38 +59,21 @@ public UtmIncidentResource(UtmIncidentService utmIncidentService, * @throws IllegalArgumentException if the input data is invalid. */ @PostMapping("/utm-incidents") + @AuditEvent( + attemptType = ApplicationEventType.INCIDENT_CREATION_ATTEMPT, + attemptMessage = "Attempt to create a new incident initiated", + successType = ApplicationEventType.INCIDENT_CREATION_SUCCESS, + successMessage = "Incident created successfully" + ) public ResponseEntity createUtmIncident(@Valid @RequestBody NewIncidentDTO newIncidentDTO) { final String ctx = ".createUtmIncident"; - try { - if (CollectionUtils.isEmpty(newIncidentDTO.getAlertList())) { - String msg = ctx + ": A new incident has to have at least one alert related"; - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.BAD_REQUEST, msg); - } - - List alertIds = newIncidentDTO.getAlertList().stream() - .map(RelatedIncidentAlertsDTO::getAlertId) - .collect(Collectors.toList()); - - List alertsFound = utmIncidentAlertService.existsAnyAlert(alertIds); - - if (!alertsFound.isEmpty()) { - String alertIdsList = String.join(", ", alertIds); - String msg = "Some alerts are already linked to another incident. Alert IDs: " + alertIdsList + ". Check the related incidents for more details."; - log.error(msg); - applicationEventService.createEvent(ctx + ": " + msg , ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.CONFLICT, utmIncidentAlertService.formatAlertMessage(alertsFound)); - } - - - return ResponseEntity.ok(utmIncidentService.createIncident(newIncidentDTO)); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR, msg); + + if (CollectionUtils.isEmpty(newIncidentDTO.getAlertList())) { + String msg = ctx + ": A new incident has to have at least one alert related"; + throw new NoAlertsProvidedException(ctx + ": " + msg); } + + return ResponseEntity.ok(utmIncidentService.createIncident(newIncidentDTO)); } /** @@ -123,36 +91,22 @@ public ResponseEntity createUtmIncident(@Valid @RequestBody NewInci * @throws URISyntaxException if the Location URI syntax is incorrect */ @PostMapping("/utm-incidents/add-alerts") + @AuditEvent( + attemptType = ApplicationEventType.INCIDENT_ALERT_ADD_ATTEMPT, + attemptMessage = "Attempt to add alerts to incident initiated", + successType = ApplicationEventType.INCIDENT_ALERT_ADD_SUCCESS, + successMessage = "Alerts added to incident successfully" + ) public ResponseEntity addAlertsToUtmIncident(@Valid @RequestBody AddToIncidentDTO addToIncidentDTO) throws URISyntaxException { - final String ctx = ".addAlertsToUtmIncident"; - try { - log.debug("REST request to save UtmIncident : {}", addToIncidentDTO); - if (CollectionUtils.isEmpty(addToIncidentDTO.getAlertList())) { - throw new BadRequestAlertException("Add utmIncident cannot already have an empty related alerts", ENTITY_NAME, "alertList"); - } - List alertIds = addToIncidentDTO.getAlertList().stream() - .map(RelatedIncidentAlertsDTO::getAlertId) - .collect(Collectors.toList()); - - List alertsFound = utmIncidentAlertService.existsAnyAlert(alertIds); - - if (!alertsFound.isEmpty()) { - String alertIdsList = String.join(", ", alertIds); - String msg = "Some alerts are already linked to another incident. Alert IDs: " + alertIdsList + ". Check the related incidents for more details."; - log.error(msg); - applicationEventService.createEvent(ctx + ": " + msg , ApplicationEventType.ERROR); - return ResponseUtil.buildErrorResponse(HttpStatus.CONFLICT, utmIncidentAlertService.formatAlertMessage(alertsFound)); - } - UtmIncident result = utmIncidentService.addAlertsIncident(addToIncidentDTO); - return ResponseEntity.created(new URI("/api/utm-incidents/add-alerts" + result.getId())) + + if (CollectionUtils.isEmpty(addToIncidentDTO.getAlertList())) { + throw new NoAlertsProvidedException("Add utmIncident cannot already have an empty related alerts"); + } + + UtmIncident result = utmIncidentService.addAlertsIncident(addToIncidentDTO); + return ResponseEntity.created(new URI("/api/utm-incidents/add-alerts" + result.getId())) .headers(HeaderUtil.createEntityCreationAlert(ENTITY_NAME, result.getId().toString())) .body(result); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(HeaderUtil.createFailureAlert(CLASS_NAME, null, msg)).body(null); - } } /** @@ -165,23 +119,22 @@ public ResponseEntity addAlertsToUtmIncident(@Valid @RequestBody Ad * @throws URISyntaxException if the Location URI syntax is incorrect */ @PutMapping("/utm-incidents/change-status") - public ResponseEntity updateUtmIncident(@Valid @RequestBody UtmIncident utmIncident) throws URISyntaxException { - final String ctx = ".updateUtmIncident"; - try { - log.debug("REST request to update UtmIncident : {}", utmIncident); - if (utmIncident.getId() == null) { - throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); - } - UtmIncident result = utmIncidentService.changeStatus(utmIncident); - return ResponseEntity.ok() + @AuditEvent( + attemptType = ApplicationEventType.INCIDENT_UPDATE_ATTEMPT, + attemptMessage = "Attempt to update incident status initiated", + successType = ApplicationEventType.INCIDENT_UPDATE_SUCCESS, + successMessage = "Incident status updated successfully" + ) + public ResponseEntity updateUtmIncident(@Valid @RequestBody UtmIncident utmIncident) { + + if (utmIncident.getId() == null) { + throw new BadRequestAlertException("Invalid id", ENTITY_NAME, "idnull"); + } + UtmIncident result = utmIncidentService.changeStatus(utmIncident); + + return ResponseEntity.ok() .headers(HeaderUtil.createEntityUpdateAlert(ENTITY_NAME, utmIncident.getId().toString())) .body(result); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(HeaderUtil.createFailureAlert(CLASS_NAME, null, msg)).body(null); - } } /** @@ -193,18 +146,10 @@ public ResponseEntity updateUtmIncident(@Valid @RequestBody UtmInci */ @GetMapping("/utm-incidents") public ResponseEntity> getAllUtmIncidents(UtmIncidentCriteria criteria, Pageable pageable) { - final String ctx = ".getAllUtmIncidents"; - try { - log.debug("REST request to get UtmIncidents by criteria: {}", criteria); - Page page = utmIncidentQueryService.findByCriteria(criteria, pageable); - HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/utm-incidents"); - return ResponseEntity.ok().headers(headers).body(page.getContent()); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(HeaderUtil.createFailureAlert(CLASS_NAME, null, msg)).body(null); - } + + Page page = utmIncidentQueryService.findByCriteria(criteria, pageable); + HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/utm-incidents"); + return ResponseEntity.ok().headers(headers).body(page.getContent()); } /** @@ -214,15 +159,7 @@ public ResponseEntity> getAllUtmIncidents(UtmIncidentCriteria */ @GetMapping("/utm-incidents/users-assigned") public ResponseEntity> getAllUserAssigned() { - final String ctx = ".getAllUserAssigned"; - try { - return ResponseEntity.ok().body(utmIncidentQueryService.getAllUsersAssigned()); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(HeaderUtil.createFailureAlert(CLASS_NAME, null, msg)).body(null); - } + return ResponseEntity.ok().body(utmIncidentQueryService.getAllUsersAssigned()); } /** @@ -233,16 +170,9 @@ public ResponseEntity> getAllUserAssigned() { */ @GetMapping("/utm-incidents/{id}") public ResponseEntity getUtmIncident(@PathVariable Long id) { - final String ctx = ".getUtmIncident"; - try { - log.debug("REST request to get UtmIncident : {}", id); - Optional utmIncident = utmIncidentService.findOne(id); - return tech.jhipster.web.util.ResponseUtil.wrapOrNotFound(utmIncident); - } catch (Exception e) { - String msg = ctx + ": " + e.getMessage(); - log.error(msg); - applicationEventService.createEvent(msg, ApplicationEventType.ERROR); - return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).headers(HeaderUtil.createFailureAlert(CLASS_NAME, null, msg)).body(null); - } + + Optional utmIncident = utmIncidentService.findOne(id); + return tech.jhipster.web.util.ResponseUtil.wrapOrNotFound(utmIncident); + } } diff --git a/backend/src/main/java/com/park/utmstack/web/rest/tfa/TfaEnrollmentResource.java b/backend/src/main/java/com/park/utmstack/web/rest/tfa/TfaEnrollmentResource.java new file mode 100644 index 000000000..99139e4d9 --- /dev/null +++ b/backend/src/main/java/com/park/utmstack/web/rest/tfa/TfaEnrollmentResource.java @@ -0,0 +1,107 @@ +package com.park.utmstack.web.rest.tfa; + +import com.park.utmstack.config.Constants; +import com.park.utmstack.domain.Authority; +import com.park.utmstack.domain.User; +import com.park.utmstack.domain.UtmConfigurationParameter; +import com.park.utmstack.security.jwt.TokenProvider; +import com.park.utmstack.service.UserService; +import com.park.utmstack.service.UtmConfigurationParameterService; +import com.park.utmstack.service.dto.jwt.LoginResponseDTO; +import com.park.utmstack.service.dto.tfa.enroll.TfaEnrollRequest; +import com.park.utmstack.service.dto.tfa.init.TfaInitResponse; +import com.park.utmstack.service.dto.tfa.save.TfaSaveRequest; +import com.park.utmstack.service.dto.tfa.verify.TfaVerifyResponse; +import com.park.utmstack.service.tfa.TfaService; +import com.park.utmstack.util.ResponseUtil; +import com.park.utmstack.util.exceptions.InvalidTfaStageException; +import io.swagger.v3.oas.annotations.Hidden; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; +import java.util.stream.Collectors; + +import static com.park.utmstack.config.Constants.PROP_TFA_METHOD; + +@RestController +@RequiredArgsConstructor +@Slf4j +@Hidden +@RequestMapping("api/enrollment/tfa") +public class TfaEnrollmentResource { + + private static final String CLASSNAME = "TfaEnrollmentController"; + + private final UserService userService; + private final TfaService tfaService; + private final UtmConfigurationParameterService utmConfigurationParameterService; + private final TokenProvider tokenProvider; + + + @PostMapping + public ResponseEntity enrollTfa(@RequestBody TfaEnrollRequest request) { + User user = userService.getCurrentUserLogin(); + + return switch (request.getStage()) { + case INIT -> { + TfaInitResponse initResponse = tfaService.initiateSetup(user, request.getMethod()); + yield ResponseEntity.ok(initResponse); + } + case VERIFY -> { + TfaVerifyResponse verifyResponse = tfaService.verifyCode(user, request.toVerifyRequest()); + yield ResponseEntity.ok(verifyResponse); + } + case COMPLETE -> { + List tfaParams = utmConfigurationParameterService + .getConfigParameterBySectionId(Constants.TFA_SETTING_ID); + + for (UtmConfigurationParameter param : tfaParams) { + switch (param.getConfParamShort()) { + case PROP_TFA_METHOD: + param.setConfParamValue(String.valueOf(request.getMethod())); + break; + case Constants.PROP_TFA_ENABLE: + param.setConfParamValue(String.valueOf(request.isEnable())); + break; + } + } + + tfaService.persistConfiguration(request.getMethod()); + utmConfigurationParameterService.saveAllConfigParams(tfaParams); + List authorities = user.getAuthorities().stream() + .map(Authority::getName) + .map(SimpleGrantedAuthority::new) + .collect(Collectors.toList()); + + org.springframework.security.core.userdetails.User principal = + new org.springframework.security.core.userdetails.User(user.getLogin(), "", authorities); + + UsernamePasswordAuthenticationToken fullAuth = + new UsernamePasswordAuthenticationToken(principal, "", authorities); + + + String fullToken = tokenProvider.createToken(fullAuth, false, true ); + + LoginResponseDTO response = LoginResponseDTO.builder() + .token(fullToken) + .method(user.getTfaMethod()) + .success(true) + .tfaConfigured(true) + .forceTfa(true) + .build(); + + yield ResponseEntity.ok(response); + } + default -> throw new InvalidTfaStageException("Invalid TFA stage: " + request.getStage()); + }; + } +} + diff --git a/backend/src/main/java/com/park/utmstack/web/rest/tfa/TfaController.java b/backend/src/main/java/com/park/utmstack/web/rest/tfa/TfaResource.java similarity index 97% rename from backend/src/main/java/com/park/utmstack/web/rest/tfa/TfaController.java rename to backend/src/main/java/com/park/utmstack/web/rest/tfa/TfaResource.java index d0cc00a0d..03d612f7f 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/tfa/TfaController.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/tfa/TfaResource.java @@ -23,12 +23,9 @@ import com.park.utmstack.util.ResponseUtil; import com.park.utmstack.util.exceptions.TfaVerificationException; import com.park.utmstack.util.exceptions.UtmMailException; -import com.park.utmstack.web.rest.util.HeaderUtil; import io.swagger.v3.oas.annotations.Hidden; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -38,7 +35,6 @@ import javax.servlet.http.HttpServletRequest; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import static com.park.utmstack.config.Constants.PROP_TFA_METHOD; @@ -46,8 +42,9 @@ @RestController @RequiredArgsConstructor @Slf4j +@Hidden @RequestMapping("api/tfa") -public class TfaController { +public class TfaResource { private static final String CLASSNAME = "TfaController"; @@ -89,8 +86,7 @@ public ResponseEntity verifyTfa(@RequestBody TfaVerifyRequest } } - @GetMapping("/generate-challenge") - @Hidden + @GetMapping("/refresh") public ResponseEntity generateChallenge() { final String ctx = CLASSNAME + ".generateChallenge"; try { @@ -152,7 +148,7 @@ public ResponseEntity completeTfa(@RequestBody TfaSaveRequest request) { successType = ApplicationEventType.AUTH_SUCCESS, successMessage = "Login successfully completed" ) - @PostMapping("/verifyCode") + @PostMapping("/verify-code") public ResponseEntity verifyCode(@RequestBody String code, HttpServletRequest request) { final String ctx = CLASSNAME + ".verifyCode"; User user = userService.getCurrentUserLogin(); diff --git a/backend/src/main/java/com/park/utmstack/web/rest/vm/LoginVM.java b/backend/src/main/java/com/park/utmstack/web/rest/vm/LoginVM.java index 74c59fd49..bf41765a5 100644 --- a/backend/src/main/java/com/park/utmstack/web/rest/vm/LoginVM.java +++ b/backend/src/main/java/com/park/utmstack/web/rest/vm/LoginVM.java @@ -1,47 +1,36 @@ package com.park.utmstack.web.rest.vm; +import com.park.utmstack.service.dto.auditable.AuditableDTO; +import lombok.Getter; +import lombok.Setter; + import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; +import java.util.HashMap; +import java.util.Map; /** * View Model object for storing a user's credentials. */ -public class LoginVM { +@Setter +public class LoginVM implements AuditableDTO { + @Getter @NotNull @Size(min = 1, max = 50) private String username; + @Getter @NotNull @Size(min = ManagedUserVM.PASSWORD_MIN_LENGTH, max = ManagedUserVM.PASSWORD_MAX_LENGTH) private String password; private Boolean rememberMe; - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - public Boolean isRememberMe() { return rememberMe; } - public void setRememberMe(Boolean rememberMe) { - this.rememberMe = rememberMe; - } - @Override public String toString() { return "LoginVM{" + @@ -49,4 +38,13 @@ public String toString() { ", rememberMe=" + rememberMe + '}'; } + + @Override + public Map toAuditMap() { + Map context = new HashMap<>(); + + context.put("loginAttempt", this.username); + + return context; + } }