Prevent data conflicts caused by database case sensitivity as possible (#7371)

#### What type of PR is this?

/kind improvement
/area core
/milestone 2.20.x

#### What this PR does / why we need it:

This PR use secure-strong SecureRandom to generate unpredictable metadata name. Meanwhile, the length of generate name suffix is increased to `8` and lower-case is to prevent data conflicts caused by database case sensitivity as possible.

Another improvement is using bounded-elastic thread to run the method `secureString()#nextAlphanumeric` because the method contains blocking operation, which might cause system block.

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/7376/head
John Niang 2025-04-22 10:09:05 +08:00 committed by GitHub
parent a94b74cb38
commit 05177544bd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 32 additions and 20 deletions

View File

@ -1,6 +1,6 @@
package run.halo.app.extension; package run.halo.app.extension;
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic; import static org.apache.commons.lang3.RandomStringUtils.secureStrong;
import static org.springframework.util.StringUtils.hasText; import static org.springframework.util.StringUtils.hasText;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
@ -31,6 +31,7 @@ import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.reactive.TransactionalOperator; import org.springframework.transaction.reactive.TransactionalOperator;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.retry.Retry; import reactor.util.retry.Retry;
import run.halo.app.extension.availability.IndexBuildState; import run.halo.app.extension.availability.IndexBuildState;
import run.halo.app.extension.exception.ExtensionNotFoundException; import run.halo.app.extension.exception.ExtensionNotFoundException;
@ -44,6 +45,8 @@ import run.halo.app.extension.store.ReactiveExtensionStoreClient;
@Component @Component
public class ReactiveExtensionClientImpl implements ReactiveExtensionClient { public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
public static final int GENERATE_NAME_RANDOM_LENGTH = 8;
private final ReactiveExtensionStoreClient client; private final ReactiveExtensionStoreClient client;
private final ExtensionConverter converter; private final ExtensionConverter converter;
@ -218,9 +221,9 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
@Override @Override
public <E extends Extension> Mono<E> create(E extension) { public <E extends Extension> Mono<E> create(E extension) {
return Mono.fromCallable(
() -> {
checkClientWritable(extension); checkClientWritable(extension);
return Mono.just(extension)
.doOnNext(ext -> {
var metadata = extension.getMetadata(); var metadata = extension.getMetadata();
// those fields should be managed by halo. // those fields should be managed by halo.
metadata.setCreationTimestamp(Instant.now()); metadata.setCreationTimestamp(Instant.now());
@ -233,12 +236,21 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
"The metadata.generateName must not be blank when metadata.name is " "The metadata.generateName must not be blank when metadata.name is "
+ "blank"); + "blank");
} }
// generate name with random text // generate name with random text
metadata.setName(metadata.getGenerateName() + randomAlphabetic(5)); // use secureStrong() to make sure the generated name is unpredictable.
metadata.setName(metadata.getGenerateName() + secureStrong()
.nextAlphanumeric(GENERATE_NAME_RANDOM_LENGTH)
// Prevent data conflicts caused by database case sensitivity
.toLowerCase()
);
} }
extension.setMetadata(metadata); extension.setMetadata(metadata);
return converter.convertTo(extension);
}) })
.map(converter::convertTo) // the method secureStrong() may invoke blocking SecureRandom, so we need to subscribe
// on boundedElastic thread pool.
.subscribeOn(Schedulers.boundedElastic())
.flatMap(extStore -> doCreate(extension, extStore.getName(), extStore.getData()) .flatMap(extStore -> doCreate(extension, extStore.getName(), extStore.getData())
.doOnNext(created -> watchers.onAdd(convertToRealExtension(created))) .doOnNext(created -> watchers.onAdd(convertToRealExtension(created)))
) )