Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release 3.3.5 #228

Merged
merged 7 commits into from
Jan 27, 2025
Merged
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
26 changes: 26 additions & 0 deletions .github/workflows/postgres.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: postgres
on:
workflow_dispatch:
inputs:
postgres:
description: "List of postgres container images, to be injected as TESTCONTAINERS_POSTGRES_IMAGE"
default: '["postgres:16-alpine", "postgres:18-alpine"]'
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
postgres: ${{ fromJSON(github.event.inputs.postgres) }}
fail-fast: false
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'
cache: maven
- run: mvn --batch-mode verify
env:
TESTCONTAINERS_POSTGRES_IMAGE: ${{ matrix.postgres }}
8 changes: 7 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
## 3.3.5 2025-01-24

* [MODRS-221](https://folio-org.atlassian.net/browse/MODRS-221): Spring Boot 3.3.6, tomcat-embed-core 10.1.33: CVE-2024-52317
* [MODRS-222](https://folio-org.atlassian.net/browse/MODRS-222): postgresql 42.7.4, embedded.db 2.6.0, postgres:16-alpine
* [MODRS-224](https://folio-org.atlassian.net/browse/MODRS-224): CQL injection, special characters cause exception

## 3.3.4 2025-01-21

* [MODRS-225](https://folio-org.atlassian.net/browse/MODRS-225) - Add missing required interfaces to Module Descriptor

## 3.3.3 2024-12-20

* [MODRS-221](https://folio-org.atlassian.net/browse/MODRS-221) - Spring Boot 3.3.6, tomcat-embed-core 10.1.33: CVE-2024-52317
Release without code change: https://github.com/folio-org/mod-remote-storage/compare/v3.3.2...v3.3.3

## 3.3.2 2024-12-02

Expand Down
15 changes: 8 additions & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version>
<version>3.3.6</version>
<relativePath />
</parent>

<groupId>org.folio</groupId>
<artifactId>mod-remote-storage</artifactId>
<version>3.3.5-SNAPSHOT</version>
<version>3.3.6-SNAPSHOT</version>
<packaging>jar</packaging>

<licenses>
Expand All @@ -27,8 +27,8 @@
<openapi-generator.version>7.3.0</openapi-generator.version>
<mapstruct.version>1.5.5.Final</mapstruct.version>
<wiremock-standalone.version>3.4.2</wiremock-standalone.version>
<embedded.db.version>2.5.0</embedded.db.version>
<postgresql.version>42.6.0</postgresql.version>
<embedded.db.version>2.6.0</embedded.db.version>
<postgresql.version>42.7.4</postgresql.version>
<pubsub.client.version>2.13.0</pubsub.client.version>
<folio-util.version>35.1.0</folio-util.version>

Expand Down Expand Up @@ -380,12 +380,13 @@
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire-plugin.version}</version>
<configuration>
<excludes>
<exclude>org/folio/rs/Controller/**Test.class</exclude>
</excludes>
<includes>
<include>**/*Test.class</include>
<include>org/folio/rs/TestSuite.class</include>
</includes>
<excludes>
<exclude>org/folio/rs/controller/**Test.class</exclude>
</excludes>
<useSystemClassLoader>false</useSystemClassLoader>
</configuration>
</plugin>
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/folio/rs/client/HoldingsStorageClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ public interface HoldingsStorageClient {
consumes = MediaType.APPLICATION_JSON_VALUE)
HoldingsRecord postHoldingsRecord(@RequestBody HoldingsRecord holdingsRecord);

@GetMapping(value = "/holdings/{id}")
HoldingsRecord getHoldingsRecord(@PathVariable("id") String id);

@PutMapping(value = "/holdings/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
void putHoldingsRecord(@PathVariable("id") String id, @RequestBody HoldingsRecord holdingsRecord);
}
18 changes: 15 additions & 3 deletions src/main/java/org/folio/rs/client/InventoryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import org.folio.rs.domain.dto.ItemsMove;
import org.folio.rs.domain.dto.ResultList;
import org.folio.rs.error.ItemReturnException;
import org.folio.util.StringUtil;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
Expand All @@ -19,6 +20,9 @@ public interface InventoryClient {
@GetMapping("/instances")
ResultList<Instance> getInstancesByQuery(@RequestParam("query") String query);

@GetMapping(value = "/instances/{id}")
Instance getInstance(@PathVariable("id") String id);

@GetMapping(value = "/items", consumes = MediaType.APPLICATION_JSON_VALUE)
ResultList<Item> getItemsByQuery(@RequestParam("query") String query);

Expand All @@ -31,11 +35,19 @@ public interface InventoryClient {
@PostMapping("/items/{id}/mark-missing")
void markItemAsMissing(@PathVariable("id") String id);

default Item getItemByBarcode(String itemBarcode) {
var items = getItemsByQuery("barcode==" + itemBarcode);
default Item getItemByBarcodeOrNull(String itemBarcode) {
var items = getItemsByQuery("barcode==" + StringUtil.cqlEncode(itemBarcode));
if (items.isEmpty()) {
throw new ItemReturnException("Item does not exist for barcode " + itemBarcode);
return null;
}
return items.getResult().get(0);
}

default Item getItemByBarcode(String itemBarcode) {
var item = getItemByBarcodeOrNull(itemBarcode);
if (item == null) {
throw new ItemReturnException("Item does not exist for barcode " + itemBarcode);
}
return item;
}
}
3 changes: 3 additions & 0 deletions src/main/java/org/folio/rs/client/UsersClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.folio.rs.domain.dto.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "users", contextId = "usersClientRS")
Expand All @@ -12,4 +13,6 @@ public interface UsersClient {
@GetMapping
ResultList<User> getUsersByQuery(@RequestParam("query") String query);

@GetMapping(value = "/{id}")
User getUser(@PathVariable("id") String id);
}
26 changes: 14 additions & 12 deletions src/main/java/org/folio/rs/service/AccessionQueueService.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.folio.rs.util.IdentifierType;
import org.folio.spring.data.OffsetRequest;
import org.folio.spring.service.SystemUserScopedExecutionService;
import org.folio.util.StringUtil;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
Expand Down Expand Up @@ -95,8 +96,7 @@ public void processAccessionQueueRecord(List<DomainEvent> events) {
var locationMapping = locationMappingsService
.getRemoteLocationConfigurationMapping(effectiveLocationId);
if (nonNull(locationMapping)) {
var instances = inventoryClient.getInstancesByQuery("id==" + item.getInstanceId());
var instance = instances.getResult().get(0);
var instance = inventoryClient.getInstance(item.getInstanceId());
var record = buildAccessionQueueRecord(item, instance, locationMapping);
accessionQueueRepository.save(record);
log.info("Record prepared and saved for item barcode: {}", record.getItemBarcode());
Expand All @@ -115,8 +115,8 @@ public AccessionQueue processPostAccession(AccessionRequest accessionRequest) {
var storageConfiguration = getStorageConfiguration(accessionRequest);
var locationMapping = getLocationMapping(accessionRequest);
var item = getItem(accessionRequest);
var holdingsRecord = holdingsStorageClient.getHoldingsRecordsByQuery("id==" + item.getHoldingsRecordId()).getResult().get(0);
var instance = inventoryClient.getInstancesByQuery("id==" + holdingsRecord.getInstanceId()).getResult().get(0);
var holdingsRecord = holdingsStorageClient.getHoldingsRecord(item.getHoldingsRecordId());
var instance = inventoryClient.getInstance(holdingsRecord.getInstanceId());

var remoteLocationId = locationMapping.getFolioLocationId();

Expand Down Expand Up @@ -156,7 +156,7 @@ private boolean isChangePermanentLocationSetting(StorageConfiguration storageCon
}

private void setLocationForItemsWithoutLocation(HoldingsRecord holdingsRecord) {
inventoryClient.getItemsByQuery("holdingsRecordId==" + holdingsRecord.getId())
inventoryClient.getItemsByQuery("holdingsRecordId==" + StringUtil.cqlEncode(holdingsRecord.getId()))
.getResult().forEach(i -> {
if (isNull(i.getPermanentLocation()) && isNull(i.getTemporaryLocation())) {
changeItemPermanentLocation(i, holdingsRecord.getPermanentLocationId());
Expand All @@ -179,7 +179,8 @@ private void changeItemPermanentLocation(Item item, String locationId) {
}

private String findOrCreateHoldingWithSamePermanentLocation(HoldingsRecord holdingsRecord, String remoteLocationId) {
var holdings = holdingsStorageClient.getHoldingsRecordsByQuery("instanceId==" + holdingsRecord.getInstanceId())
var cql = "instanceId==" + StringUtil.cqlEncode(holdingsRecord.getInstanceId());
var holdings = holdingsStorageClient.getHoldingsRecordsByQuery(cql)
.getResult().stream()
.filter(h -> remoteLocationId.equals(h.getPermanentLocationId()))
.collect(Collectors.toList());
Expand All @@ -205,19 +206,19 @@ private void moveItemToHolding(Item item, String holdingRecordId) {
}

private boolean isAllItemsInHoldingHaveSamePermanentLocation(Item item, String location) {
return inventoryClient.getItemsByQuery("holdingsRecordId==" + item.getHoldingsRecordId())
return inventoryClient.getItemsByQuery("holdingsRecordId==" + StringUtil.cqlEncode(item.getHoldingsRecordId()))
.getResult()
.stream()
.filter(i -> !item.getId().equals(i.getId()))
.allMatch(i -> nonNull(i.getPermanentLocation()) && location.equals(i.getPermanentLocation().getId()));
}

private Item getItem(AccessionRequest accessionRequest) {
var items = inventoryClient.getItemsByQuery("barcode==" + accessionRequest.getItemBarcode());
if (items.isEmpty()) {
var item = inventoryClient.getItemByBarcodeOrNull(accessionRequest.getItemBarcode());
if (item == null) {
throw new AccessionException(String.format("Item with barcode=%s was not found", accessionRequest.getItemBarcode()));
}
return items.getResult().get(0);
return item;
}

private StorageConfiguration getStorageConfiguration(AccessionRequest accessionRequest) {
Expand Down Expand Up @@ -319,7 +320,8 @@ private AccessionQueueRecord buildAccessionQueueRecord(Item item, Instance insta
}

private String extractAuthors(Instance instance) {
var contributorTypeId = contributorTypesClient.getContributorTypesByQuery("name==" + AUTHOR)
var cql = "name==" + StringUtil.cqlEncode(AUTHOR.toString());
var contributorTypeId = contributorTypesClient.getContributorTypesByQuery(cql)
.getResult()
.stream()
.findFirst()
Expand All @@ -340,7 +342,7 @@ private String extractContributors(Instance instance) {
}

private String extractIdentifier(Instance instance, IdentifierType type) {
var identifierTypeId = identifierTypesClient.getIdentifierTypesByQuery("name==" + type)
var identifierTypeId = identifierTypesClient.getIdentifierTypesByQuery("name==" + StringUtil.cqlEncode(type.toString()))
.getResult()
.stream()
.findFirst()
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/org/folio/rs/service/ItemsService.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public class ItemsService {

public void markItemAsMissingByBarcode(String barcode) {
log.debug("markItemAsMissingByBarcode :: barcode:{}",barcode);
var items = inventoryClient.getItemsByQuery("barcode==" + barcode);
if (!items.isEmpty()) {
inventoryClient.markItemAsMissing(items.getResult().get(0).getId());
var item = inventoryClient.getItemByBarcodeOrNull(barcode);
if (item != null) {
inventoryClient.markItemAsMissing(item.getId());
}
}
}
9 changes: 4 additions & 5 deletions src/main/java/org/folio/rs/service/ReturnItemService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static org.folio.rs.domain.entity.ProviderRecord.CAIA_SOFT;
import static org.folio.rs.util.RetrievalQueueRecordUtils.buildReturnRetrievalRecord;

import feign.FeignException;
import java.util.Optional;

import org.folio.rs.client.CirculationClient;
Expand All @@ -33,8 +34,6 @@
@RequiredArgsConstructor
@Log4j2
public class ReturnItemService {
private static final String USER_ID_QUERY_PROPERTY = "id==";

private final InventoryClient inventoryClient;
private final CirculationClient circulationClient;
private final UsersClient usersClient;
Expand Down Expand Up @@ -117,10 +116,10 @@ private Optional<Request> findFirstHoldRecallRequest(Item item) {
}

private User getUserById(String requesterId) {
var users = usersClient.getUsersByQuery(USER_ID_QUERY_PROPERTY + requesterId);
if (users.isEmpty()) {
try {
return usersClient.getUser(requesterId);
} catch (FeignException.NotFound e) {
throw new ItemReturnException("User does not exist for requester id " + requesterId);
}
return users.getResult().get(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static org.folio.rs.util.MapperUtils.stringToUUIDSafe;
import static org.folio.rs.util.RetrievalQueueRecordUtils.buildReturnRetrievalQueueRecord;

import feign.FeignException;
import java.time.LocalDateTime;
import java.util.*;

Expand Down Expand Up @@ -166,18 +167,18 @@ private RemoteLocationConfigurationMapping getRemoteLocationConfigurationMapping
}

private Item getOriginalItemByBarcode(RequestEvent requestEvent) {
ResultList<Item> items = inventoryClient.getItemsByQuery("barcode==" + requestEvent.getItemBarCode());
if (isEmpty(items.getResult())) {
var item = inventoryClient.getItemByBarcodeOrNull(requestEvent.getItemBarCode());
if (item == null) {
throw new EntityNotFoundException("Item with barcode " + requestEvent.getItemBarCode() + NOT_FOUND);
}
return items.getResult().get(0);
return item;
}

private User getUserByRequesterId(RequestEvent requestEvent) {
ResultList<User> users = usersClient.getUsersByQuery("id==" + requestEvent.getRequesterId());
if (isEmpty(users.getResult())) {
try {
return usersClient.getUser(requestEvent.getRequesterId());
} catch (FeignException.NotFound e) {
throw new EntityNotFoundException("User with id " + requestEvent.getRequesterId() + NOT_FOUND);
}
return users.getResult().get(0);
}
}
16 changes: 16 additions & 0 deletions src/test/java/org/folio/rs/EmbeddedPostgresConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package org.folio.rs;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.zonky.test.db.provider.postgres.PostgreSQLContainerCustomizer;

@Configuration
public class EmbeddedPostgresConfiguration {
private static final String IMAGE_NAME =
System.getenv().getOrDefault("TESTCONTAINERS_POSTGRES_IMAGE", "postgres:16-alpine");

@Bean
public PostgreSQLContainerCustomizer postgresContainerCustomizer() {
return container -> container.setDockerImageName(IMAGE_NAME);
}
}
Loading