Added logging mechanism for user actions.

This commit is contained in:
piyushkag
2024-11-20 12:03:09 +05:30
parent bab6fcfad6
commit 6eafa7b33e
26 changed files with 798 additions and 42 deletions

View File

@@ -0,0 +1,26 @@
package net.gepafin.tendermanagement.config;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class CachedBodyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest httpServletRequest) {
CachedBodyHttpServletRequest cachedRequest = new CachedBodyHttpServletRequest(httpServletRequest);
chain.doFilter(cachedRequest, response);
} else {
chain.doFilter(request, response);
}
}
}

View File

@@ -0,0 +1,76 @@
package net.gepafin.tendermanagement.config;
import jakarta.servlet.ReadListener;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
public class CachedBodyHttpServletRequest extends HttpServletRequestWrapper {
private final byte[] cachedBody;
public CachedBodyHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
InputStream requestInputStream = request.getInputStream();
this.cachedBody = requestInputStream.readAllBytes();
}
@Override
public ServletInputStream getInputStream() {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
return new ServletInputStreamWrapper(byteArrayInputStream);
}
@Override
public BufferedReader getReader() throws IOException {
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(cachedBody);
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
}
public String getCachedBodyAsString() {
return new String(cachedBody);
}
private static class ServletInputStreamWrapper extends ServletInputStream {
private final ByteArrayInputStream byteArrayInputStream;
public ServletInputStreamWrapper(ByteArrayInputStream byteArrayInputStream) {
this.byteArrayInputStream = byteArrayInputStream;
}
@Override
public int read() throws IOException {
return byteArrayInputStream.read();
}
@Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
@Override
public boolean isReady() {
return true;
}
@Override
public void setReadListener(ReadListener readListener) {
}
}
}

View File

@@ -0,0 +1,25 @@
package net.gepafin.tendermanagement.config;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class RequestCachingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest cachedRequest = new CachedBodyHttpServletRequest((HttpServletRequest) request);
chain.doFilter(cachedRequest, response);
} else {
chain.doFilter(request, response);
}
}
}

View File

@@ -0,0 +1,29 @@
package net.gepafin.tendermanagement.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
@Component
public class UniqueSessionInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
request.getSession(true);
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
if ("/v1/user/logout".equals(request.getRequestURI())) {
HttpSession session = request.getSession(false);
if (session != null) {
session.invalidate();
}
}
}
}

View File

@@ -6,6 +6,7 @@ import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.gepafin.tendermanagement.config.Translator;
import net.gepafin.tendermanagement.constants.GepafinConstant;
import net.gepafin.tendermanagement.entities.UserEntity;
@@ -15,6 +16,7 @@ import net.gepafin.tendermanagement.web.rest.api.errors.Status;
import net.gepafin.tendermanagement.web.rest.api.errors.UnauthorizedAccessException;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.http.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -23,6 +25,7 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
@@ -49,9 +52,11 @@ public class TokenProvider {
@Autowired
private UserRepository userRepository;
@Autowired
HttpServletResponse response;
private SecretKey key;
private static final String AUTHORITIES_KEY = "auth";
private static final String MERCHANTID="merchantId";
static final String AUTH_SECRET = "X-Api-Secret";
@@ -82,11 +87,11 @@ public class TokenProvider {
log.info("JWT Secret Key initialized.");
}
public String createToken(Boolean rememberMe, UserEntity user) {
// String authorities = authentication.getAuthorities().stream()
// .map(GrantedAuthority::getAuthority)
// .collect(Collectors.joining(","));
String authorities = user.getRoleEntity().getRoleType();
public String createToken(Boolean rememberMe, UserEntity user, Long loginAttemptId) {
// String authorities = authentication.getAuthorities().stream()
// .map(GrantedAuthority::getAuthority)
// .collect(Collectors.joining(","));
String authorities = user.getRoleEntity().getRoleType();
Long now;
Date validity;
@@ -103,19 +108,19 @@ public class TokenProvider {
String payload = user.getEmail();
if(user != null) {
payload += ":"+user.getId();
}
if(user != null) {
payload += ":"+user.getHub().getId();
}
String token = Jwts.builder()
.setSubject(payload)
.claim("auth", authorities)
.claim(GepafinConstant.LOGIN_ATTEMPT_ID, loginAttemptId)
.claim(GepafinConstant.USER_ID, user.getId() != null ? user.getId() : null)
.claim(GepafinConstant.AUTH, authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
response.setHeader("Authorization", "Bearer " + token);
log.debug("Generated token: {}", token);
return token;
}
@@ -249,4 +254,26 @@ public class TokenProvider {
return null; // Return null if token is not found or not in Bearer format
}
public Claims getClaimsFromToken(String token) {
return Jwts.parser().setSigningKey(key).parseClaimsJws(token).getBody();
}
public String getCurrentActiveUserEmail() {
var authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.isAuthenticated()) {
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
String email = userDetails.getUsername();
int lastColonIndex = email.lastIndexOf(":");
int secondLastColonIndex = email.lastIndexOf(":", lastColonIndex - 1);
if (secondLastColonIndex != -1) {
return email.substring(0, secondLastColonIndex);
} else {
return email;
}
}
return null;
}
}

View File

@@ -292,5 +292,11 @@ public class GepafinConstant {
public static final String ENCRYPT_KEY = "U2VjdXJlRW5jcnlwdEtleQ==";
public static final String APPLICATION_DOCUMENTS_NOT_FOUND_MSG = "application.documents.not.found";
public static final String DUPLICATE_BENEFICIARY_CALL = "beneficiary.call.duplicate";
public static final String AUTH = "auth";
//Logging
public static final String USER_ID = "userId";
public static final String LOGIN_ATTEMPT_ID = "loginAttemptId";
public static final String USER_ACTION_ID = "userActionId";
}

View File

@@ -15,10 +15,13 @@ import java.util.zip.ZipOutputStream;
import jakarta.servlet.http.HttpServletRequest;
import net.gepafin.tendermanagement.entities.*;
import net.gepafin.tendermanagement.enums.DocumentSourceTypeEnum;
import net.gepafin.tendermanagement.enums.VersionActionTypeEnum;
import net.gepafin.tendermanagement.model.request.VersionHistoryRequest;
import net.gepafin.tendermanagement.model.response.*;
import net.gepafin.tendermanagement.repositories.*;
import net.gepafin.tendermanagement.service.*;
import net.gepafin.tendermanagement.util.DateTimeUtil;
import net.gepafin.tendermanagement.util.LoggingUtil;
import net.gepafin.tendermanagement.util.Utils;
import net.gepafin.tendermanagement.util.Validator;
import org.h2.util.IOUtils;
@@ -95,6 +98,11 @@ public class CallDao {
@Autowired
private Validator validator;
@Autowired
private LoggingUtil loggingUtil;
@Autowired
private HttpServletRequest request;
public CallResponse createCallStep1(CreateCallRequestStep1 createCallRequest, UserEntity userEntity) {
createCallRequest.setRegionId(userEntity.getRoleEntity().getRegion().getId());
@@ -144,7 +152,8 @@ public class CallDao {
public CallEntity convertToCallEntity(CreateCallRequestStep1 createCallRequest, UserEntity userEntity) {
CallEntity callEntity = new CallEntity();
// validateCallEntity(createCallRequest);
VersionHistoryRequest versionHistoryRequest = new VersionHistoryRequest();
// validateCallEntity(createCallRequest);
RegionEntity region = regionRepository.findById(createCallRequest.getRegionId())
.orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND,
Translator.toLocale(GepafinConstant.REGION_NOT_FOUND)));
@@ -180,6 +189,11 @@ public class CallDao {
callEntity.setEndTime(DateTimeUtil.parseTime(createCallRequest.getEndTime()));
callEntity.setHub(userEntity.getHub());
callEntity = callRepository.save(callEntity);
versionHistoryRequest.setOldData(null);
versionHistoryRequest.setNewData(callEntity);
versionHistoryRequest.setRequest(request);
versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT);
loggingUtil.addVersionHistory(versionHistoryRequest);
return callEntity;
}
@@ -197,7 +211,15 @@ public class CallDao {
List<EvaluationCriteriaEntity> evaluationCriteriaEntities = criteriaReqList.stream()
.map(req -> convertToEvaluationCriteriaEntity(req, callEntity, type)).collect(Collectors.toList());
evaluationCriteriaRepository.saveAll(evaluationCriteriaEntities);
List<EvaluationCriteriaEntity> data = evaluationCriteriaRepository.saveAll(evaluationCriteriaEntities);
data.forEach(entity -> {
VersionHistoryRequest versionHistoryRequest = new VersionHistoryRequest();
versionHistoryRequest.setOldData(null);
versionHistoryRequest.setNewData(entity);
versionHistoryRequest.setRequest(request);
versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT);
loggingUtil.addVersionHistory(versionHistoryRequest);
});
return evaluationCriteriaEntities;
}
@@ -394,6 +416,7 @@ public class CallDao {
private List<LookUpDataResponse> createCallTargetAudienceCheckList(CallEntity callEntity,
List<LookUpDataEntity> lookUpDataEntities) {
List<LookUpDataResponse> lookUpDataResponses = new ArrayList<>();
VersionHistoryRequest versionHistoryRequest = new VersionHistoryRequest();
for (LookUpDataEntity lookUpDataEntity : lookUpDataEntities) {
CallTargetAudienceChecklistEntity callTargetAudienceChecklistEntity = new CallTargetAudienceChecklistEntity();
callTargetAudienceChecklistEntity.setIsValidated(false);
@@ -402,6 +425,11 @@ public class CallDao {
callTargetAudienceChecklistEntity.setIsDeleted(false);
callTargetAudienceChecklistEntity = callTargetAudienceChecklistRepository
.save(callTargetAudienceChecklistEntity);
versionHistoryRequest.setOldData(null);
versionHistoryRequest.setNewData(callTargetAudienceChecklistEntity);
versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT);
versionHistoryRequest.setRequest(request);
loggingUtil.addVersionHistory(versionHistoryRequest);
lookUpDataResponses.add(convertToLookUpDataResponseBean(callTargetAudienceChecklistEntity));
}
return lookUpDataResponses;

View File

@@ -1,20 +1,24 @@
package net.gepafin.tendermanagement.dao;
import jakarta.servlet.http.HttpServletRequest;
import net.gepafin.tendermanagement.config.Translator;
import net.gepafin.tendermanagement.constants.GepafinConstant;
import net.gepafin.tendermanagement.entities.CallEntity;
import net.gepafin.tendermanagement.entities.FaqEntity;
import net.gepafin.tendermanagement.entities.LookUpDataEntity;
import net.gepafin.tendermanagement.entities.UserEntity;
import net.gepafin.tendermanagement.enums.RoleStatusEnum;
import net.gepafin.tendermanagement.entities.LookUpDataEntity.LookUpDataTypeEnum;
import net.gepafin.tendermanagement.enums.VersionActionTypeEnum;
import net.gepafin.tendermanagement.model.request.FaqReq;
import net.gepafin.tendermanagement.model.request.VersionHistoryRequest;
import net.gepafin.tendermanagement.model.response.FaqResponseBean;
import net.gepafin.tendermanagement.repositories.FaqRepository;
import net.gepafin.tendermanagement.service.CallService;
import net.gepafin.tendermanagement.service.CompanyService;
import net.gepafin.tendermanagement.service.LookUpDataService;
import net.gepafin.tendermanagement.util.DateTimeUtil;
import net.gepafin.tendermanagement.util.LoggingUtil;
import net.gepafin.tendermanagement.util.Utils;
import net.gepafin.tendermanagement.util.Validator;
import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException;
import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException;
@@ -45,10 +49,16 @@ public class FaqDao {
@Autowired
private CompanyService companyService;
@Autowired
HttpServletRequest request;
@Autowired
LoggingUtil loggingUtil;
public FaqResponseBean createFaq(FaqReq faqRequest, UserEntity userEntity, Long callId, Long companyId) {
CallEntity callEntity = callService.validateCall(callId);
FaqEntity entity = createOrUpdateFaqEntity(faqRequest, callEntity, userEntity,
LookUpDataEntity.LookUpDataTypeEnum.FAQ);
LookUpDataTypeEnum.FAQ);
if (validator.checkIsBeneficiary() && companyId == null) {
throw new CustomValidationException(Status.VALIDATION_ERROR,
Translator.toLocale(GepafinConstant.COMPANY_ID_MANDATORY));
@@ -68,7 +78,7 @@ public class FaqDao {
public FaqResponseBean updateFaq(Long id, FaqReq faqRequest, UserEntity userEntity) {
FaqEntity entity = validateFaq(id);
faqRequest.setId(entity.getId());
createOrUpdateFaqEntity(faqRequest, entity.getCall(), userEntity, LookUpDataEntity.LookUpDataTypeEnum.FAQ);
createOrUpdateFaqEntity(faqRequest, entity.getCall(), userEntity, LookUpDataTypeEnum.FAQ);
return convertToFaqResponseBean(entity);
}
@@ -93,10 +103,13 @@ public class FaqDao {
public FaqEntity createOrUpdateFaqEntity(FaqReq faqReq, CallEntity callEntity, UserEntity userEntity,
LookUpDataTypeEnum type) {
FaqEntity faqEntity = null;
FaqEntity oldFaqData = null;
VersionHistoryRequest versionHistoryRequest = new VersionHistoryRequest();
if (isExistingFaq(faqReq)) {
faqEntity = faqRepository.findByIdAndCallIdAndIsDeletedFalse(faqReq.getId(), callEntity.getId())
.orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND,
Translator.toLocale(GepafinConstant.FAQ_NOT_FOUND)));
oldFaqData = Utils.getClonedEntityForData(faqEntity);
} else {
if (Boolean.FALSE.equals(userEntity.getRoleEntity().getRoleType().equals(RoleStatusEnum.ROLE_BENEFICIARY.getValue()))) {
lookUpDataService.getOrCreateLookUpDataEntity(faqReq, type);
@@ -115,7 +128,13 @@ public class FaqDao {
setIfUpdated(faqEntity::getValue, faqEntity::setValue, faqReq.getValue());
setIfUpdated(faqEntity::getResponse, faqEntity::setResponse, faqReq.getResponse());
setIfUpdated(faqEntity::getIsVisible, faqEntity::setIsVisible, faqReq.getIsVisible());
return faqRepository.save(faqEntity);
FaqEntity newFaqData = faqRepository.save(faqEntity);
versionHistoryRequest.setOldData(oldFaqData);
versionHistoryRequest.setNewData(newFaqData);
versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT);
versionHistoryRequest.setRequest(request);
loggingUtil.addVersionHistory(versionHistoryRequest);
return newFaqData;
}
private boolean isExistingFaq(FaqReq faqReq) {

View File

@@ -1,11 +1,17 @@
package net.gepafin.tendermanagement.dao;
import jakarta.servlet.http.HttpServletRequest;
import net.gepafin.tendermanagement.entities.LookUpDataEntity;
import net.gepafin.tendermanagement.entities.LookUpDataEntity.LookUpDataTypeEnum;
import net.gepafin.tendermanagement.enums.UserActionLogsEnum;
import net.gepafin.tendermanagement.enums.VersionActionTypeEnum;
import net.gepafin.tendermanagement.model.request.LookUpDataReq;
import net.gepafin.tendermanagement.model.request.LookUpDataRequest;
import net.gepafin.tendermanagement.model.request.VersionHistoryRequest;
import net.gepafin.tendermanagement.model.response.LookUpDataResponseBean;
import net.gepafin.tendermanagement.repositories.LookUpDataRepository;
import net.gepafin.tendermanagement.util.LoggingUtil;
import net.gepafin.tendermanagement.util.Utils;
import net.gepafin.tendermanagement.web.rest.api.errors.CustomValidationException;
import net.gepafin.tendermanagement.web.rest.api.errors.ResourceNotFoundException;
import net.gepafin.tendermanagement.web.rest.api.errors.Status;
@@ -25,6 +31,12 @@ public class LookUpDataDao {
@Autowired
private LookUpDataRepository lookUpDataRepository;
@Autowired
HttpServletRequest request;
@Autowired
LoggingUtil loggingUtil;
public LookUpDataResponseBean createLookUpData(LookUpDataRequest lookUpDataReq) {
LookUpDataEntity entity = convertLookUpDataReqToLookUpDataEntity(lookUpDataReq);
return convertLookUpDataEntityToResponseBean(entity);
@@ -87,18 +99,25 @@ public class LookUpDataDao {
.map(this::convertLookUpDataEntityToResponseBean)
.collect(Collectors.toList());
}
public LookUpDataEntity getOrCreateLookUpDataEntity(LookUpDataReq req,
LookUpDataEntity.LookUpDataTypeEnum type) {
if (req.getLookUpDataId() == null || req.getLookUpDataId().equals(0l)) {
LookUpDataEntity newEntity = new LookUpDataEntity();
newEntity.setTitle(req.getTitle());
newEntity.setValue(req.getValue());
newEntity.setResponse(req.getResponse());
newEntity.setType(type.getValue());
validateLookUpDataEntity(newEntity);
return lookUpDataRepository.save(newEntity);
}
public LookUpDataEntity getOrCreateLookUpDataEntity(LookUpDataReq req, LookUpDataEntity.LookUpDataTypeEnum type) {
if (req.getLookUpDataId() == null || req.getLookUpDataId().equals(0L)) {
LookUpDataEntity newEntity = new LookUpDataEntity();
VersionHistoryRequest versionHistoryRequest = new VersionHistoryRequest();
newEntity.setTitle(req.getTitle());
newEntity.setValue(req.getValue());
newEntity.setResponse(req.getResponse());
newEntity.setType(type.getValue());
validateLookUpDataEntity(newEntity);
LookUpDataEntity lookUpDataEntity = lookUpDataRepository.save(newEntity);
versionHistoryRequest.setOldData(null);
versionHistoryRequest.setNewData(lookUpDataEntity);
versionHistoryRequest.setActionType(VersionActionTypeEnum.INSERT);
versionHistoryRequest.setRequest(request);
loggingUtil.addVersionHistory(versionHistoryRequest);
return lookUpDataEntity;
}
return lookUpDataRepository.findById(req.getLookUpDataId())
.orElseThrow(() -> new ResourceNotFoundException(Status.NOT_FOUND,

View File

@@ -94,13 +94,14 @@ public class UserDao {
log.info("User created with ID: {}", userEntity.getId());
LoginReq loginReq=new LoginReq();
loginReq.setEmail(userEntity.getEmail());
LoginAttemptEntity loginAttemptEntity = null;
if(userEntity!=null){
LoginAttemptEntity loginAttemptEntity =authenticationService.prepareLoginAttemptEntity(loginReq, request);
loginAttemptEntity = authenticationService.prepareLoginAttemptEntity(loginReq, request);
log.info("Authentication failed for email: {}", loginReq.getEmail());
loginAttemptEntity.setUserId(userEntity.getId());
authenticationService.createSuccessLoginAttempt(loginAttemptEntity);
}
return authService.getJWTTokenBean(userEntity, Boolean.TRUE);
return authService.getJWTTokenBean(userEntity, Boolean.TRUE, loginAttemptEntity.getId());
}
private BeneficiaryEntity createBeneficiary(RoleEntity roleEntity, UserReq userReq, HubEntity hub) {

View File

@@ -0,0 +1,43 @@
package net.gepafin.tendermanagement.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
@Data
@Entity
@Table(name = "user_action")
public class UserActionEntity extends BaseEntity {
@Column(name = "USER_ID")
private Long userId;
@Column(name = "ACTION_TYPE")
private String actionType;
@Column(name = "REQUEST_BODY")
private String requestBody;
@Column(name = "LOGIN_ATTEMPT_ID")
private Long loginAttemptId;
@Column(name = "IP_ADDRESS")
private String ipAddress;
@Column(name = "METHOD_TYPE")
private String methodType;
@Column(name = "HUB_ID")
private Long hubId;
@Column(name = "URL")
private String url;
@Column(name = "ACTION_CONTEXT")
private String actionContext;
@Column(name = "RESPONSE")
private Object response;
}

View File

@@ -0,0 +1,33 @@
package net.gepafin.tendermanagement.entities;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.Data;
@Data
@Entity
@Table(name = "version_history")
public class VersionHistoryEntity extends BaseEntity {
@Column(name = "OLD_DATA", columnDefinition = "LONGTEXT")
private String oldData;
@Column(name = "NEW_DATA", columnDefinition = "LONGTEXT")
private String newData;
@Column(name = "TABLE_NAME")
private String tableName;
@Column(name = "ACTION_TYPE")
private String actionType;
@Column(name = "RECORD_ID")
private Long recordId;
@Column(name = "USER_ACTION_ID")
private Long userActionId;
@Column(name = "USER_ID")
private Long userId;
}

View File

@@ -0,0 +1,20 @@
package net.gepafin.tendermanagement.enums;
import com.fasterxml.jackson.annotation.JsonValue;
public enum UserActionContextEnum {
CREATE_CALL("CREATE_CALL"),;
private final String value;
UserActionContextEnum(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,20 @@
package net.gepafin.tendermanagement.enums;
import com.fasterxml.jackson.annotation.JsonValue;
public enum UserActionLogsEnum {
LOGIN("LOGIN"), LOGOUT("LOGOUT"), UPDATE("UPDATE"), DELETE("DELETE"), VIEW("VIEW"), INSERT("INSERT");
private final String value;
UserActionLogsEnum(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,20 @@
package net.gepafin.tendermanagement.enums;
import com.fasterxml.jackson.annotation.JsonValue;
public enum VersionActionTypeEnum {
UPDATE("UPDATE"), DELETE("DELETE"), SOFT_DELETE("SOFT_DELETE"), INSERT("INSERT");
private final String value;
VersionActionTypeEnum(String value) {
this.value = value;
}
@JsonValue
public String getValue() {
return value;
}
}

View File

@@ -0,0 +1,12 @@
package net.gepafin.tendermanagement.model.request;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import net.gepafin.tendermanagement.enums.UserActionLogsEnum;
@Data
public class UserActionRequest {
private HttpServletRequest request;
private UserActionLogsEnum actionType;
private String actionContext;
}

View File

@@ -0,0 +1,17 @@
package net.gepafin.tendermanagement.model.request;
import jakarta.servlet.http.HttpServletRequest;
import lombok.Data;
import net.gepafin.tendermanagement.entities.BaseEntity;
import net.gepafin.tendermanagement.enums.VersionActionTypeEnum;
@Data
public class VersionHistoryRequest {
private BaseEntity oldData;
private BaseEntity newData;
private VersionActionTypeEnum actionType;
private HttpServletRequest request;
private Long userActionId;
private Long recordId;
private String tableName;
}

View File

@@ -0,0 +1,28 @@
package net.gepafin.tendermanagement.model.util;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import net.gepafin.tendermanagement.web.rest.api.errors.Status;
import java.io.Serial;
import java.io.Serializable;
@JsonIgnoreProperties(ignoreUnknown = true)
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class LogResponse<T> implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Integer status;
private String statusCode;
private String message;
}

View File

@@ -0,0 +1,9 @@
package net.gepafin.tendermanagement.repositories;
import net.gepafin.tendermanagement.entities.UserActionEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface UserActionsRepository extends JpaRepository<UserActionEntity, Long> {
}

View File

@@ -0,0 +1,9 @@
package net.gepafin.tendermanagement.repositories;
import net.gepafin.tendermanagement.entities.VersionHistoryEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface VersionHistoryRepository extends JpaRepository<VersionHistoryEntity, Long> {
}

View File

@@ -103,7 +103,7 @@ public class AuthenticationService {
createFailedLoginAttempt(loginAttemptEntity, e.getMessage());
throw e;
}
return getJWTTokenBean(user, loginReq.getRememberMe());
return getJWTTokenBean(user, loginReq.getRememberMe(), loginAttemptEntity.getId());
}
public LoginAttemptEntity prepareLoginAttemptEntity(LoginReq loginUserReq, HttpServletRequest request) {
@@ -126,10 +126,10 @@ public class AuthenticationService {
loginAttemptEntity.setErrorMsg(errorMsg);
loginAttemptDao.createLoginAttempt(loginAttemptEntity);
}
public JWTToken getJWTTokenBean(UserEntity user, Boolean rememberMe) {
public JWTToken getJWTTokenBean(UserEntity user, Boolean rememberMe, Long loginAttemptId) {
user.setLastLogin(DateTimeUtil.DateServerToUTC(LocalDateTime.now()));
userRepository.save(user);
String token = tokenProvider.createToken(rememberMe, user);
user = userRepository.save(user);
String token = tokenProvider.createToken(rememberMe, user, loginAttemptId);
log.info("JWT token generated for email: {}", user.getEmail());
RoleResponseBean roleResponseBean = roleDao.convertRoleEntityToRoleResponse(user.getRoleEntity());
@@ -215,7 +215,7 @@ public class AuthenticationService {
loginReq.setEmail(userEntity.getEmail());
loginAttemptEntity = prepareLoginAttemptEntity(loginReq, request);
loginAttemptEntity.setUserId(userEntity.getId());
return getJWTTokenBean(userEntity, Boolean.TRUE);
return getJWTTokenBean(userEntity, Boolean.TRUE, loginAttemptEntity.getId());
} catch (Exception e) {
log.info("Authentication login failed for email: {}",e.getMessage());
loginAttemptEntity.setUserId(userId);

View File

@@ -0,0 +1,45 @@
package net.gepafin.tendermanagement.util;
import jakarta.servlet.http.HttpServletRequest;
import java.util.regex.Pattern;
public class IpAddressUtils {
private static final Pattern IPV4_PATTERN = Pattern.compile("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
private static final Pattern IPV6_PATTERN = Pattern.compile(
"([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|" + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + "::(ffff(:0{1,4}){0,1}:){0,1}(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3,3}" + "([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])|([0-9a-fA-F]{1,4}:){1,4}:" + "(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3,3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])");
public static String getClientIp(HttpServletRequest request) {
String ipAddress = null;
String[] headers = { "X-Forwarded-For", "X-Real-IP", "Proxy-Client-IP", "WL-Proxy-Client-IP", "HTTP_CLIENT_IP", "HTTP_X_FORWARDED_FOR" };
for (String header : headers) {
ipAddress = request.getHeader(header);
if (ipAddress != null && !ipAddress.isEmpty() && !"unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = ipAddress.split(",")[0].trim();
break;
}
}
if (ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)) {
ipAddress = request.getRemoteAddr();
}
if ("0:0:0:0:0:0:0:1".equals(ipAddress) || "127.0.0.1".equals(ipAddress)) {
ipAddress = "127.0.0.1";
}
if (isValidIpAddress(ipAddress)) {
return ipAddress;
} else {
return "Invalid IP";
}
}
private static boolean isValidIpAddress(String ipAddress) {
return IPV4_PATTERN.matcher(ipAddress).matches() || IPV6_PATTERN.matcher(ipAddress).matches();
}
}

View File

@@ -0,0 +1,149 @@
package net.gepafin.tendermanagement.util;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import jakarta.persistence.Table;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import net.gepafin.tendermanagement.config.CachedBodyHttpServletRequest;
import net.gepafin.tendermanagement.config.jwt.TokenProvider;
import net.gepafin.tendermanagement.constants.GepafinConstant;
import net.gepafin.tendermanagement.entities.UserActionEntity;
import net.gepafin.tendermanagement.entities.UserEntity;
import net.gepafin.tendermanagement.entities.VersionHistoryEntity;
import net.gepafin.tendermanagement.model.request.UserActionRequest;
import net.gepafin.tendermanagement.model.request.VersionHistoryRequest;
import net.gepafin.tendermanagement.model.util.LogResponse;
import net.gepafin.tendermanagement.repositories.UserActionsRepository;
import net.gepafin.tendermanagement.repositories.VersionHistoryRepository;
import net.gepafin.tendermanagement.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.annotation.RequestScope;
@Slf4j
@Component
@RequestScope
public class LoggingUtil {
@Autowired
private UserActionsRepository userActionsRepository;
@Autowired
private VersionHistoryRepository versionHistoryRepository;
@Autowired
private UserService userService;
@Autowired
TokenProvider tokenProvider;
public UserActionEntity logUserAction(UserActionRequest userActionRequest) {
UserActionEntity userAction = new UserActionEntity();
try {
String token = tokenProvider.extractTokenFromRequest(userActionRequest.getRequest());
Claims claims = tokenProvider.getClaimsFromToken(token);
Long userId = claims.get(GepafinConstant.USER_ID, Long.class);
Long loginAttemptId = claims.get(GepafinConstant.LOGIN_ATTEMPT_ID, Long.class);
UserEntity userEntity = userService.validateUser(userId);
String requestBody = null;
if (userActionRequest.getRequest() instanceof CachedBodyHttpServletRequest cachedRequest) {
requestBody = cachedRequest.getCachedBodyAsString();
}
userAction.setActionType(userActionRequest.getActionType().getValue());
userAction.setUserId(userId);
userAction.setActionContext(userActionRequest.getActionContext());
userAction.setMethodType(userActionRequest.getRequest().getMethod());
userAction.setHubId(userEntity.getHub().getId());
userAction.setUrl(userActionRequest.getRequest().getRequestURI());
userAction.setLoginAttemptId(loginAttemptId);
userAction.setIpAddress(IpAddressUtils.getClientIp(userActionRequest.getRequest()));
userAction.setRequestBody(requestBody);
userAction.setResponse(null);
userActionsRepository.save(userAction);
userActionRequest.getRequest().setAttribute(GepafinConstant.USER_ACTION_ID, userAction.getId());
} catch (Exception e) {
log.error("Error logging user action: {}", e.getMessage(), e);
}
return userAction;
}
// @Transactional
// public UserActionEntity updateUserAction(HttpServletResponse response, UserActionEntity userAction) {
// try {
// String requestBody = null;
// if (userAction. instanceof CachedBodyHttpServletRequest cachedRequest) {
// requestBody = cachedRequest.getCachedBodyAsString();
// }
//
// LogResponse<String> logResponse = new LogResponse<>();
// logResponse.setStatus(response.getStatus());
// logResponse.setStatusCode(String.valueOf(response.getStatus()));
// logResponse.setMessage("SUCCESS");
//
// String serializedResponse;
// try {
// ObjectMapper objectMapper = new ObjectMapper();
// serializedResponse = objectMapper.writeValueAsString(logResponse);
// } catch (Exception e) {
// serializedResponse = "Error serializing LogResponse: " + e.getMessage();
// }
//
// userAction.setResponse(serializedResponse);
// userActionsRepository.save(userAction);
// } catch (Exception e) {
// log.error("Error updating user action: {}", e.getMessage(), e);
// }
// return userAction;
// }
public void logVersionHistory(VersionHistoryRequest versionHistoryRequest) {
try {
VersionHistoryEntity history = new VersionHistoryEntity();
String token = tokenProvider.extractTokenFromRequest(versionHistoryRequest.getRequest());
Claims claims = tokenProvider.getClaimsFromToken(token);
Long userId = claims.get(GepafinConstant.USER_ID, Long.class);
String oldData = Utils.convertEntityToJsonForLogging(versionHistoryRequest.getOldData());
String newData = Utils.convertEntityToJsonForLogging(versionHistoryRequest.getNewData());
history.setUserActionId(versionHistoryRequest.getUserActionId());
history.setActionType(versionHistoryRequest.getActionType().getValue());
history.setOldData(oldData);
history.setNewData(newData);
history.setRecordId(versionHistoryRequest.getRecordId());
history.setTableName(versionHistoryRequest.getTableName());
history.setUserId(userId);
versionHistoryRepository.save(history);
} catch (Exception e) {
log.error("Error logging version history: {}", e.getMessage(), e);
}
}
public String getTableName(Class<?> entityClass) {
try {
if (entityClass.isAnnotationPresent(Table.class)) {
Table tableAnnotation = entityClass.getAnnotation(Table.class);
return tableAnnotation.name();
}
} catch (Exception e) {
log.error("Error retrieving table name: {}", e.getMessage(), e);
}
return null;
}
public void addVersionHistory(VersionHistoryRequest versionHistoryRequest) {
try {
Long userActionId = (Long) versionHistoryRequest.getRequest().getAttribute(GepafinConstant.USER_ACTION_ID);
Long recordId = versionHistoryRequest.getNewData().getId();
String tableName = getTableName(versionHistoryRequest.getNewData().getClass());
versionHistoryRequest.setRecordId(recordId);
versionHistoryRequest.setUserActionId(userActionId);
versionHistoryRequest.setTableName(tableName);
logVersionHistory(versionHistoryRequest);
} catch (Exception e) {
log.error("Error adding version history: {}", e.getMessage(), e);
}
}
}

View File

@@ -5,20 +5,19 @@ import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.*;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import com.itextpdf.styledxmlparser.jsoup.Jsoup;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.SerializationFeature;
import jakarta.servlet.http.HttpServletRequest;
import net.gepafin.tendermanagement.constants.GepafinConstant;
import org.apache.commons.collections4.MapUtils;
@@ -515,4 +514,36 @@ public class Utils {
}
return null;
}
public static String convertEntityToJsonForLogging(Object entity) {
if(entity == null){
return null;
}
try {
return mapper.writeValueAsString(entity);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
@JsonIgnoreProperties({ "childEntities", "relatedData", "otherRelations" })
private abstract static class IgnoreChildEntities {
}
public static <T> T getClonedEntityForData(T entity) {
if (entity == null) {
return null;
}
try {
String json = mapper.writeValueAsString(entity);
@SuppressWarnings("unchecked") T clonedEntity = (T) mapper.readValue(json, entity.getClass());
return clonedEntity;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("Failed to clone entity", e);
}
}
}

View File

@@ -2,7 +2,13 @@ package net.gepafin.tendermanagement.web.rest.api.impl;
import java.util.List;
import lombok.extern.slf4j.Slf4j;
import net.gepafin.tendermanagement.enums.CallStatusEnum;
import net.gepafin.tendermanagement.enums.UserActionContextEnum;
import net.gepafin.tendermanagement.enums.UserActionLogsEnum;
import net.gepafin.tendermanagement.model.request.UserActionRequest;
import net.gepafin.tendermanagement.repositories.UserActionsRepository;
import net.gepafin.tendermanagement.util.LoggingUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
@@ -33,11 +39,22 @@ public class CallApiController implements CallApi {
@Autowired
private CallService callService;
@Autowired
private LoggingUtil loggingUtil;
@Autowired
private UserActionsRepository userActionsRepository;
@Override
@Transactional(rollbackFor=Exception.class)
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<Response<CallResponse>> createCallStep1(HttpServletRequest request, CreateCallRequestStep1 createCallRequest) {
CallResponse createCallResponseBean = callService.createCallStep1(request, createCallRequest);
UserActionRequest userActionRequest = new UserActionRequest();
userActionRequest.setRequest(request);
userActionRequest.setActionType(UserActionLogsEnum.INSERT);
userActionRequest.setActionContext(UserActionContextEnum.CREATE_CALL.getValue());
loggingUtil.logUserAction(userActionRequest);
CallResponse createCallResponseBean = callService.createCallStep1(request, createCallRequest);
return ResponseEntity.status(HttpStatus.CREATED)
.body(new Response<>(createCallResponseBean, Status.SUCCESS, Translator.toLocale(GepafinConstant.CALL_CREATED_SUCCESSFULLY_MSG)));
}

View File

@@ -1732,4 +1732,51 @@
</column>
</addColumn>
</changeSet>
<changeSet id="20-11-2024_1" author="Piyush">
<createTable tableName="user_action">
<column autoIncrement="true" name="id" type="INTEGER">
<constraints nullable="false" primaryKey="true" primaryKeyName="pk_user_action"/>
</column>
<column name="user_id" type="INTEGER"/>
<column name="action_type" type="TEXT"/>
<column name="request_body" type="TEXT"/>
<column name="login_attempt_id" type="INTEGER"/>
<column name="ip_address" type="TEXT"/>
<column name="action_context" type="TEXT"/>
<column name="method_type" type="TEXT"/>
<column name="hub_id" type="INTEGER"/>
<column name="url" type="TEXT"/>
<column name="response" type="TEXT"/>
<column name="created_date" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated_date" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
</changeSet>
<changeSet id="20-11-2024_2" author="Piyush">
<createTable tableName="version_history">
<column autoIncrement="true" name="id" type="INTEGER">
<constraints nullable="false" primaryKey="true" primaryKeyName="pk_version_history"/>
</column>
<column name="old_data" type="LONGTEXT"/>
<column name="new_data" type="LONGTEXT"/>
<column name="table_name" type="TEXT"/>
<column name="action_type" type="TEXT"/>
<column name="record_id" type="INTEGER"/>
<column name="user_id" type="INTEGER"/>
<column name="user_action_id" type="INTEGER">
<constraints nullable="false"/>
</column>
<column name="created_date" type="TIMESTAMP WITHOUT TIME ZONE"/>
<column name="updated_date" type="TIMESTAMP WITHOUT TIME ZONE"/>
</createTable>
<addForeignKeyConstraint baseTableName="version_history"
baseColumnNames="user_action_id"
referencedTableName="user_action"
referencedColumnNames="id"
onDelete="CASCADE"
constraintName="fk_version_history_user_action"/>
</changeSet>
</databaseChangeLog>