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;
import static org.apache.commons.lang3.RandomStringUtils.randomAlphabetic;
import static org.apache.commons.lang3.RandomStringUtils.secureStrong;
import static org.springframework.util.StringUtils.hasText;
import com.fasterxml.jackson.databind.ObjectMapper;
@ -31,6 +31,7 @@ import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.reactive.TransactionalOperator;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
import reactor.util.retry.Retry;
import run.halo.app.extension.availability.IndexBuildState;
import run.halo.app.extension.exception.ExtensionNotFoundException;
@ -44,6 +45,8 @@ import run.halo.app.extension.store.ReactiveExtensionStoreClient;
@Component
public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
public static final int GENERATE_NAME_RANDOM_LENGTH = 8;
private final ReactiveExtensionStoreClient client;
private final ExtensionConverter converter;
@ -218,27 +221,36 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
@Override
public <E extends Extension> Mono<E> create(E extension) {
checkClientWritable(extension);
return Mono.just(extension)
.doOnNext(ext -> {
var metadata = extension.getMetadata();
// those fields should be managed by halo.
metadata.setCreationTimestamp(Instant.now());
metadata.setDeletionTimestamp(null);
metadata.setVersion(null);
return Mono.fromCallable(
() -> {
checkClientWritable(extension);
var metadata = extension.getMetadata();
// those fields should be managed by halo.
metadata.setCreationTimestamp(Instant.now());
metadata.setDeletionTimestamp(null);
metadata.setVersion(null);
if (!hasText(metadata.getName())) {
if (!hasText(metadata.getGenerateName())) {
throw new IllegalArgumentException(
"The metadata.generateName must not be blank when metadata.name is "
+ "blank");
if (!hasText(metadata.getName())) {
if (!hasText(metadata.getGenerateName())) {
throw new IllegalArgumentException(
"The metadata.generateName must not be blank when metadata.name is "
+ "blank");
}
// generate name with random text
// 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()
);
}
// generate name with random text
metadata.setName(metadata.getGenerateName() + randomAlphabetic(5));
}
extension.setMetadata(metadata);
})
.map(converter::convertTo)
extension.setMetadata(metadata);
return converter.convertTo(extension);
})
// 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())
.doOnNext(created -> watchers.onAdd(convertToRealExtension(created)))
)