Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
rootProject.name = "spring-undo"
include(":spring-undo-core")
include(":spring-undo-redis")
include(":spring-undo-redis")
include(":spring-undo-mongo")
18 changes: 18 additions & 0 deletions spring-undo-mongo/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Java_conventions_gradle.Versions

plugins {
id("java-conventions")
id("testing-conventions")
id("publishing-conventions")
}

description = "Spring-Undo persistence module. Stores records in mongodb and provides access to them."

dependencies {
compileOnly("org.springframework.boot:spring-boot-starter-data-mongodb:${Versions.springBootVersion}")
compileOnly(project(":spring-undo-core"))

testImplementation("org.springframework.boot:spring-boot-starter-data-mongodb:${Versions.springBootVersion}")
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critical OSS Vulnerability:

pkg:maven/org.springframework.boot/[email protected]

1 Critical, 1 Severe, 0 Moderate, 0 Unknown vulnerabilities have been found across 2 dependencies

Components
    pkg:maven/org.springframework/[email protected]
      SEVERE Vulnerabilities (1)

        [CVE-2022-22970] CWE-770: Allocation of Resources Without Limits or Throttling

        In spring framework versions prior to 5.3.20+ , 5.2.22+ and old unsupported versions, applications that handle file uploads are vulnerable to DoS attack if they rely on data binding to set a MultipartFile or javax.servlet.Part to a field in a model object.

        CVSS Score: 5.3

        CVSS Vector: CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H

        CWE: CWE-770

    pkg:maven/org.springframework.data/[email protected]
      CRITICAL Vulnerabilities (1)

        [CVE-2022-22980] CWE-917: Improper Neutralization of Special Elements used in an Expression Language Statement ('Expression Language Injection')

        A Spring Data MongoDB application is vulnerable to SpEL Injection when using @query or @Aggregation-annotated query methods with SpEL expressions that contain query parameter placeholders for value binding if the input is not sanitized.

        CVSS Score: 9

        CVSS Vector: CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:H/I:H/A:H

        CWE: CWE-917

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

testImplementation("org.testcontainers:testcontainers:1.17.1")
testImplementation(project(":spring-undo-core"))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.fomenko.springundomongo;

import dev.fomenko.springundocore.dto.ActionRecord;

public final class MongoConverter {

private MongoConverter() {
}

public static RecordEntity<?> toEntity(ActionRecord<?> dto) {
return RecordEntity.builder()
.action(dto.getAction())
.expiresAt(dto.getExpiresAt())
.recordId(dto.getRecordId())
.build();
}


public static ActionRecord<?> toDto(RecordEntity<?> dto) {
return ActionRecord.builder()
.action(dto.getAction())
.expiresAt(dto.getExpiresAt())
.recordId(dto.getRecordId())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package dev.fomenko.springundomongo;


import com.mongodb.client.result.DeleteResult;
import dev.fomenko.springundocore.dto.ActionRecord;
import dev.fomenko.springundocore.service.EventRecorder;
import lombok.RequiredArgsConstructor;
import lombok.extern.apachecommons.CommonsLog;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;


@CommonsLog
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SameNameButDifferent: The name @CommonsLog refers to [java.lang.SuppressWarnings, org.apache.commons.logging.Log, org.apache.commons.logging.LogFactory] within this file. It may be confusing to have the same name refer to multiple types. Consider qualifying them for clarity.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

@RequiredArgsConstructor
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SameNameButDifferent: The name @RequiredArgsConstructor refers to [java.lang.SuppressWarnings, org.springframework.data.mongodb.core.MongoTemplate] within this file. It may be confusing to have the same name refer to multiple types. Consider qualifying them for clarity.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

public class MongoEventRecorder implements EventRecorder {
public static final String COLLECTION_NAME = "recoverableEvents";

private final MongoTemplate mongoTemplate;

@Override
public void saveRecord(ActionRecord<?> actionRecord) {
RecordEntity<?> recordEntity = MongoConverter.toEntity(actionRecord);
mongoTemplate.save(recordEntity, COLLECTION_NAME);
}

@Override
public List<ActionRecord<?>> getAllRecords() {
List<RecordEntity> results = mongoTemplate.findAll(RecordEntity.class, COLLECTION_NAME);
return results.stream()
.map(MongoConverter::toDto)
.collect(Collectors.toList());
}

@Override
public Optional<ActionRecord<?>> getRecordById(String recordId) {
Query query = new Query(Criteria.where("recordId").is(recordId));
RecordEntity<?> recordEntity = mongoTemplate.findOne(query, RecordEntity.class, COLLECTION_NAME);
return Optional.of(MongoConverter.toDto(recordEntity));
}

@Override
public boolean deleteRecordById(String recordId) {
Query query = new Query(Criteria.where("recordId").is(recordId));
DeleteResult deleteResult = mongoTemplate.remove(query, RecordEntity.class, COLLECTION_NAME);
long deletedCount = deleteResult.getDeletedCount();
return deletedCount > 0;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dev.fomenko.springundomongo;

import dev.fomenko.springundocore.service.EventRecorder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.core.MongoTemplate;

@Configuration
public class MongoUndoRecorderConfiguration {

@Bean
public EventRecorder mongoEventRecorder(MongoTemplate mongoTemplate) {
return new MongoEventRecorder(mongoTemplate);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package dev.fomenko.springundomongo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

import java.time.LocalDateTime;

@Data
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SameNameButDifferent: The name @Data refers to [java.lang.SuppressWarnings, java.lang.String, java.time.LocalDateTime, java.lang.Override, java.lang.Object] within this file. It may be confusing to have the same name refer to multiple types. Consider qualifying them for clarity.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

@Builder
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SameNameButDifferent: The name @Builder refers to [java.lang.SuppressWarnings, java.lang.String, java.time.LocalDateTime, dev.fomenko.springundomongo.RecordEntity.RecordEntityBuilder, dev.fomenko.springundomongo.RecordEntity, java.lang.Override] within this file. It may be confusing to have the same name refer to multiple types. Consider qualifying them for clarity.

Suggested change
@Builder
RecordEntity.@Builder

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MissingSummary: A summary fragment is required; consider using the value of the @return block as a summary fragment instead.

Suggested change
@Builder
Returns {@code this}.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

@AllArgsConstructor
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SameNameButDifferent: The name @AllArgsConstructor refers to [java.lang.SuppressWarnings, java.lang.String, java.time.LocalDateTime] within this file. It may be confusing to have the same name refer to multiple types. Consider qualifying them for clarity.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

@NoArgsConstructor
@Document
public class RecordEntity<T> {
@Id
private String recordId;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SameNameButDifferent: The name recordId; refers to [java.lang.SuppressWarnings, java.lang.String] within this file. It may be confusing to have the same name refer to multiple types. Consider qualifying them for clarity.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

private T action;
private LocalDateTime expiresAt;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SameNameButDifferent: The name expiresAt; refers to [java.lang.SuppressWarnings, java.time.LocalDateTime] within this file. It may be confusing to have the same name refer to multiple types. Consider qualifying them for clarity.

Reply with "@sonatype-lift help" for info about LiftBot commands.
Reply with "@sonatype-lift ignore" to tell LiftBot to leave out the above finding from this PR.
Reply with "@sonatype-lift ignoreall" to tell LiftBot to leave out all the findings from this PR and from the status bar in Github.

When talking to LiftBot, you need to refresh the page to see its response. Click here to get to know more about LiftBot commands.


Was this a good recommendation?
[ 🙁 Not relevant ] - [ 😕 Won't fix ] - [ 😑 Not critical, will fix ] - [ 🙂 Critical, will fix ] - [ 😊 Critical, fixing now ]

}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=dev.fomenko.springundomongo.MongoUndoRecorderConfiguration
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package dev.fomenko.springundomongo;

import dev.fomenko.springundocore.dto.ActionRecord;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.mongodb.core.MongoTemplate;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;

import static dev.fomenko.springundomongo.MongoEventRecorder.COLLECTION_NAME;

@SpringBootTest
class MongoEventRecorderTest {
@Autowired
private MongoEventRecorder mongoEventRecorder;

@Autowired
private MongoTemplate mongoTemplate;

@AfterEach
void setup() {
mongoTemplate.dropCollection(COLLECTION_NAME);
}


@Test
void shouldSaveRecordCorrectly() {
//given
String recordId = "record_name";
LocalDateTime expiresAt = LocalDateTime.now();
String actionName = "action1";
String description = "desc";
int size = 382;

ActionRecord<TestDto> actionTest = ActionRecord.<TestDto>builder()
.recordId(recordId)
.action(TestDto.builder()
.testName(actionName)
.testDescription(description)
.size(size).build())
.expiresAt(expiresAt).build();

//when
mongoEventRecorder.saveRecord(actionTest);

//then
List<RecordEntity> recordEntities = mongoTemplate.findAll(RecordEntity.class, COLLECTION_NAME);
RecordEntity<TestDto> record = recordEntities.get(0);

Assertions.assertEquals(1, recordEntities.size());
Assertions.assertEquals(actionName, record.getAction().getTestName());
Assertions.assertEquals(description, record.getAction().getTestDescription());
Assertions.assertEquals(size, record.getAction().getSize());
Assertions.assertEquals(recordId, record.getRecordId());
}


@Test
void shouldGetAllRecords() {
//given
LocalDateTime expiresAt1 = LocalDateTime.now();
String recordId1 = "event1";
String testName1 = "name1";
ActionRecord<TestDto> actionTest1 = ActionRecord.<TestDto>builder()
.recordId(recordId1)
.action(TestDto.builder()
.testName(testName1)
.testDescription("desc")
.size(382).build())
.expiresAt(expiresAt1).build();


LocalDateTime expiresAt2 = LocalDateTime.now();
String recordId2 = "event2";
String testName2 = "name2";
ActionRecord<TestDto> actionTest2 = ActionRecord.<TestDto>builder()
.recordId(recordId2)
.action(TestDto.builder()
.testName(testName2)
.testDescription("desc")
.size(382).build())
.expiresAt(expiresAt2).build();

//when
mongoTemplate.save(MongoConverter.toEntity(actionTest1), COLLECTION_NAME);
mongoTemplate.save(MongoConverter.toEntity(actionTest2), COLLECTION_NAME);

//then
List<ActionRecord<?>> allRecords = mongoEventRecorder.getAllRecords();
Assertions.assertEquals(2, allRecords.size());

Assertions.assertEquals(recordId1, allRecords.get(0).getRecordId());
Assertions.assertEquals(testName1, ((TestDto) allRecords.get(0).getAction()).getTestName());

Assertions.assertEquals(recordId2, allRecords.get(1).getRecordId());
Assertions.assertEquals(testName2, ((TestDto) allRecords.get(1).getAction()).getTestName());
}


@Test
void shouldGetRecordById() {
//given
String description = "description01";
String recordId = "record01";
int size = 2;
String testName = "test01";

ActionRecord<TestDto> actionTest1 = ActionRecord.<TestDto>builder()
.recordId(recordId)
.action(TestDto.builder()
.testName(testName)
.testDescription(description)
.size(size)
.build())
.build();

ActionRecord<TestDto> actionTest2 = ActionRecord.<TestDto>builder()
.recordId("record02")
.action(TestDto.builder()
.testDescription("description02")
.size(12)
.build())
.build();

mongoTemplate.save(MongoConverter.toEntity(actionTest1), COLLECTION_NAME);
mongoTemplate.save(MongoConverter.toEntity(actionTest2), COLLECTION_NAME);

//when
Optional<ActionRecord<?>> recordById = mongoEventRecorder.getRecordById(recordId);

//then
ActionRecord<TestDto> resultRecord = (ActionRecord<TestDto>) recordById.get();

Assertions.assertEquals(recordId, resultRecord.getRecordId());
Assertions.assertEquals(testName, resultRecord.getAction().getTestName());
Assertions.assertEquals(description, resultRecord.getAction().getTestDescription());
Assertions.assertEquals(size, resultRecord.getAction().getSize());
}


@Test
void shouldDeleteRecordById() {
//given
String recordId1 = "event1";
ActionRecord<TestDto> actionTest1 = ActionRecord.<TestDto>builder()
.recordId(recordId1)
.action(TestDto.builder().build())
.expiresAt(LocalDateTime.now()).build();

String recordId2 = "event2";
ActionRecord<TestDto> actionTest2 = ActionRecord.<TestDto>builder()
.recordId(recordId2)
.action(TestDto.builder().build())
.expiresAt(LocalDateTime.now()).build();

mongoTemplate.save(MongoConverter.toEntity(actionTest1), COLLECTION_NAME);
mongoTemplate.save(MongoConverter.toEntity(actionTest2), COLLECTION_NAME);

//when
boolean wasDeleted = mongoEventRecorder.deleteRecordById(recordId2);


//then
List<RecordEntity> results = mongoTemplate.findAll(RecordEntity.class, COLLECTION_NAME);

Assertions.assertTrue(wasDeleted);
Assertions.assertEquals(1, results.size());
Assertions.assertEquals(recordId1, results.get(0).getRecordId());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package dev.fomenko.springundomongo;

import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.testcontainers.containers.GenericContainer;

@SpringBootApplication
public class MongoEventRecorderTestConfiguration extends AbstractMongoClientConfiguration {
private static final int MONGO_PORT = 27017;

private final static GenericContainer<?> mongo = new GenericContainer<>("mongo:5.0.8")
.withExposedPorts(MONGO_PORT);

static {
mongo.start();
}

@Override
protected String getDatabaseName() {
return "test";
}


@Override
public MongoClient mongoClient() {
String url = String.format("mongodb://%s:%s", mongo.getHost(), mongo.getMappedPort(MONGO_PORT));
return MongoClients.create(MongoClientSettings.builder()
.applyConnectionString(new ConnectionString(url))
.build());
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package dev.fomenko.springundomongo;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TestDto implements Serializable {
private String testName;
private String testDescription;
private Integer size;
}