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

fix: failure to create notification templates in themes #7199

Merged
merged 1 commit into from
Jan 20, 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
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
import java.nio.file.Path;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -41,7 +43,10 @@
import run.halo.app.core.extension.AnnotationSetting;
import run.halo.app.core.extension.Setting;
import run.halo.app.core.extension.Theme;
import run.halo.app.core.extension.notification.NotificationTemplate;
import run.halo.app.extension.ConfigMap;
import run.halo.app.extension.Extension;
import run.halo.app.extension.GroupVersionKind;
import run.halo.app.extension.MetadataUtil;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.Unstructured;
Expand Down Expand Up @@ -114,7 +119,7 @@ private static Mono<Path> createThemeTempPath() {
public Mono<Theme> install(Publisher<DataBuffer> content) {
var themeRoot = this.themeRoot.get();
return unzipThemeTo(content, themeRoot, scheduler)
.flatMap(this::persistent);
.flatMap(theme -> persistent(theme, false));
}

@Override
Expand Down Expand Up @@ -155,7 +160,7 @@ public Mono<Theme> upgrade(String themeName, Publisher<DataBuffer> content) {

return deleteThemeAndWaitForComplete(themeName)
.then(copyTheme)
.then(this.persistent(newTheme));
.then(this.persistent(newTheme, true));
});
},
tempDir -> deleteRecursivelyAndSilently(tempDir, scheduler)
Expand All @@ -173,7 +178,7 @@ public Mono<Theme> upgrade(String themeName, Publisher<DataBuffer> content) {
* @return a theme custom model
* @see Theme
*/
public Mono<Theme> persistent(Unstructured themeManifest) {
private Mono<Theme> persistent(Unstructured themeManifest, boolean isUpgrade) {
Assert.state(StringUtils.equals(Theme.KIND, themeManifest.getKind()),
"Theme manifest kind must be Theme.");
return client.create(themeManifest)
Expand Down Expand Up @@ -204,34 +209,27 @@ public Mono<Theme> persistent(Unstructured themeManifest) {
return Mono.error(new IllegalStateException(
"Theme must only have one config.yaml or config.yml."));
}
var spec = theme.getSpec();
return Flux.fromIterable(unstructureds)
.filter(unstructured -> {
String name = unstructured.getMetadata().getName();
boolean isThemeSetting = unstructured.getKind().equals(Setting.KIND)
&& StringUtils.equals(spec.getSettingName(), name);

boolean isThemeConfig = unstructured.getKind().equals(ConfigMap.KIND)
&& StringUtils.equals(spec.getConfigMapName(), name);

boolean isAnnotationSetting = unstructured.getKind()
.equals(AnnotationSetting.KIND);
return isThemeSetting || isThemeConfig || isAnnotationSetting;
})
.filter(unstructured -> ExtensionWhitelist.of(theme).isAllowed(unstructured))
.doOnNext(unstructured ->
populateThemeNameLabel(unstructured, theme.getMetadata().getName()))
.flatMap(this::createOrUpdate)
.flatMap(unstructured -> {
if (isUpgrade) {
return createOrUpdate(unstructured);
}
return client.create(unstructured);
})
.then(Mono.just(theme));
});
}

Mono<Unstructured> createOrUpdate(Unstructured unstructured) {
private Mono<Unstructured> createOrUpdate(Unstructured unstructured) {
return Mono.defer(() -> client.fetch(unstructured.groupVersionKind(),
unstructured.getMetadata().getName())
.flatMap(existUnstructured -> {
existUnstructured.getMetadata()
.setVersion(unstructured.getMetadata().getVersion());
return client.update(existUnstructured);
unstructured.getMetadata()
.setVersion(existUnstructured.getMetadata().getVersion());
return client.update(unstructured);
})
.switchIfEmpty(Mono.defer(() -> client.create(unstructured)))
)
Expand Down Expand Up @@ -264,17 +262,12 @@ public Mono<Theme> reloadTheme(String name) {
})
.flatMap(client::update);
}))
.flatMap(theme -> {
String settingName = theme.getSpec().getSettingName();
return Flux.fromIterable(ThemeUtils.loadThemeResources(getThemePath(theme)))
.filter(unstructured -> (Setting.KIND.equals(unstructured.getKind())
&& unstructured.getMetadata().getName().equals(settingName))
|| AnnotationSetting.KIND.equals(unstructured.getKind())
)
.doOnNext(unstructured -> populateThemeNameLabel(unstructured, name))
.flatMap(client::create)
.then(Mono.just(theme));
});
.flatMap(theme -> Flux.fromIterable(ThemeUtils.loadThemeResources(getThemePath(theme)))
.filter(unstructured -> ExtensionWhitelist.of(theme).isAllowed(unstructured))
.doOnNext(unstructured -> populateThemeNameLabel(unstructured, name))
.flatMap(this::createOrUpdate)
.then(Mono.just(theme))
);
}

private static void populateThemeNameLabel(Unstructured unstructured, String themeName) {
Expand Down Expand Up @@ -373,4 +366,61 @@ Mono<Void> waitForThemeDeleted(String themeName) {
new ServerErrorException("Wait timeout for theme deleted", null)))
.then();
}

static class ExtensionWhitelist {
private final Set<AllowedExtension> rules;

private ExtensionWhitelist(Theme theme) {
this.rules = getRules(theme);
}

public static ExtensionWhitelist of(Theme theme) {
return new ExtensionWhitelist(theme);
}

public boolean isAllowed(Unstructured unstructured) {
return this.rules.stream()
.anyMatch(rule -> rule.matches(unstructured));
}

private Set<AllowedExtension> getRules(Theme theme) {
var rules = new HashSet<AllowedExtension>();
rules.add(AllowedExtension.of(AnnotationSetting.class));
rules.add(AllowedExtension.of(NotificationTemplate.class));

var configMapName = theme.getSpec().getConfigMapName();
if (StringUtils.isNotBlank(configMapName)) {
rules.add(AllowedExtension.of(ConfigMap.class, configMapName));
}

var settingName = theme.getSpec().getSettingName();
if (StringUtils.isNotBlank(settingName)) {
rules.add(AllowedExtension.of(Setting.class, settingName));
}
return rules;
}
}

record AllowedExtension(String apiGroup, String kind, String name) {
AllowedExtension {
Assert.notNull(apiGroup, "The apiGroup must not be null");
Assert.notNull(kind, "Kind must not be null");
}

public static <E extends Extension> AllowedExtension of(Class<E> clazz) {
return of(clazz, null);
}

public static <E extends Extension> AllowedExtension of(Class<E> clazz, String name) {
var gvk = GroupVersionKind.fromExtension(clazz);
return new AllowedExtension(gvk.group(), gvk.kind(), name);
}

public boolean matches(Unstructured unstructured) {
var groupVersionKind = unstructured.groupVersionKind();
return this.apiGroup.equals(groupVersionKind.group())
&& this.kind.equals(groupVersionKind.kind())
&& (this.name == null || this.name.equals(unstructured.getMetadata().getName()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,8 @@ void reloadThemeWhenSettingNameNotSetBefore() throws IOException {

when(client.list(eq(AnnotationSetting.class), any(), eq(null))).thenReturn(Flux.empty());

when(client.fetch(eq(Setting.GVK), eq("fake-setting")))
.thenReturn(Mono.empty());
themeService.reloadTheme("fake-theme")
.as(StepVerifier::create)
.consumeNextWith(themeUpdated -> {
Expand Down
Loading