mirror of https://github.com/halo-dev/halo
feat: allow theme preview for theme admins when preview is disabled (#7277)
#### What type of PR is this? /kind feature /area core /milestone 2.20.x #### What this PR does / why we need it: 支持禁用主题预览功能,但拥有主题管理权限的用户不受此功能影响 #### Which issue(s) this PR fixes: Fixes #7204 #### Does this PR introduce a user-facing change? ```release-note 支持禁用主题预览功能,但拥有主题管理权限的用户不受此功能影响 ```pull/7290/head
parent
fed80f26f2
commit
6e6bb42778
|
@ -32,6 +32,7 @@ public class SystemSetting {
|
|||
public static class ThemeRouteRules {
|
||||
public static final String GROUP = "routeRules";
|
||||
|
||||
private boolean disableThemePreview;
|
||||
private String categories;
|
||||
private String archives;
|
||||
private String post;
|
||||
|
|
|
@ -28,6 +28,8 @@ public enum AuthorityUtils {
|
|||
|
||||
public static final String POST_CONTRIBUTOR_ROLE_NAME = "role-template-post-contributor";
|
||||
|
||||
public static final String THEME_MANAGEMENT_ROLE_NAME = "role-template-manage-themes";
|
||||
|
||||
/**
|
||||
* Converts an array of GrantedAuthority objects to a role set.
|
||||
*
|
||||
|
|
|
@ -1,14 +1,25 @@
|
|||
package run.halo.app.theme;
|
||||
|
||||
import static run.halo.app.security.authorization.AuthorityUtils.authoritiesToRoles;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.user.service.RoleService;
|
||||
import run.halo.app.infra.AnonymousUserConst;
|
||||
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
|
||||
import run.halo.app.infra.SystemSetting.Theme;
|
||||
import run.halo.app.infra.SystemSetting.ThemeRouteRules;
|
||||
import run.halo.app.infra.ThemeRootGetter;
|
||||
import run.halo.app.security.authorization.AuthorityUtils;
|
||||
|
||||
/**
|
||||
* @author johnniang
|
||||
|
@ -21,6 +32,7 @@ public class ThemeResolver {
|
|||
private final SystemConfigurableEnvironmentFetcher environmentFetcher;
|
||||
|
||||
private final ThemeRootGetter themeRoot;
|
||||
private final RoleService roleService;
|
||||
|
||||
public Mono<ThemeContext> getThemeContext(String themeName) {
|
||||
Assert.hasText(themeName, "Theme name cannot be empty");
|
||||
|
@ -39,17 +51,17 @@ public class ThemeResolver {
|
|||
|
||||
public Mono<ThemeContext> getTheme(ServerWebExchange exchange) {
|
||||
return fetchThemeFromExchange(exchange)
|
||||
.switchIfEmpty(Mono.defer(() -> environmentFetcher.fetch(Theme.GROUP, Theme.class)
|
||||
.map(Theme::getActive)
|
||||
.switchIfEmpty(
|
||||
Mono.error(() -> new IllegalArgumentException("No theme activated")))
|
||||
.map(activatedTheme -> {
|
||||
.switchIfEmpty(Mono.defer(() -> fetchActivationState()
|
||||
.map(themeState -> {
|
||||
var activatedTheme = themeState.activatedTheme();
|
||||
var builder = ThemeContext.builder();
|
||||
var themeName = exchange.getRequest().getQueryParams()
|
||||
.getFirst(ThemeContext.THEME_PREVIEW_PARAM_NAME);
|
||||
if (StringUtils.isBlank(themeName)) {
|
||||
|
||||
if (StringUtils.isBlank(themeName) || !themeState.supportsPreviewTheme()) {
|
||||
themeName = activatedTheme;
|
||||
}
|
||||
|
||||
boolean active = StringUtils.equals(activatedTheme, themeName);
|
||||
var path = themeRoot.get().resolve(themeName);
|
||||
return builder.name(themeName)
|
||||
|
@ -70,4 +82,44 @@ public class ThemeResolver {
|
|||
.cast(ThemeContext.class);
|
||||
}
|
||||
|
||||
private Mono<ThemeActivationState> fetchActivationState() {
|
||||
var builder = ThemeActivationState.builder();
|
||||
|
||||
var activatedMono = environmentFetcher.fetch(Theme.GROUP, Theme.class)
|
||||
.map(Theme::getActive)
|
||||
.switchIfEmpty(Mono.error(() -> new IllegalArgumentException("No theme activated")))
|
||||
.doOnNext(builder::activatedTheme);
|
||||
|
||||
var preivewDisabledMono = environmentFetcher.fetch(ThemeRouteRules.GROUP,
|
||||
ThemeRouteRules.class)
|
||||
.map(ThemeRouteRules::isDisableThemePreview)
|
||||
.defaultIfEmpty(false)
|
||||
.doOnNext(builder::previewDisabled);
|
||||
|
||||
var themeManageMono = ReactiveSecurityContextHolder.getContext()
|
||||
.map(SecurityContext::getAuthentication)
|
||||
.filter(au -> !AnonymousUserConst.isAnonymousUser(au.getName()))
|
||||
.flatMap(au -> supportsPreviewTheme(authoritiesToRoles(au.getAuthorities())))
|
||||
.doOnNext(builder::hasThemeManagementRole);
|
||||
|
||||
return Mono.when(activatedMono, preivewDisabledMono, themeManageMono)
|
||||
.then(Mono.fromSupplier(builder::build));
|
||||
}
|
||||
|
||||
private Mono<Boolean> supportsPreviewTheme(Collection<String> authorities) {
|
||||
return roleService.contains(authorities, Set.of(AuthorityUtils.THEME_MANAGEMENT_ROLE_NAME))
|
||||
.defaultIfEmpty(false);
|
||||
}
|
||||
|
||||
@Builder
|
||||
record ThemeActivationState(String activatedTheme, boolean previewDisabled,
|
||||
boolean hasThemeManagementRole) {
|
||||
|
||||
private boolean supportsPreviewTheme() {
|
||||
if (hasThemeManagementRole) {
|
||||
return true;
|
||||
}
|
||||
return !previewDisabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -142,6 +142,11 @@ spec:
|
|||
- group: routeRules
|
||||
label: 主题路由设置
|
||||
formSchema:
|
||||
- $formkit: checkbox
|
||||
label: "关闭主题预览"
|
||||
value: false
|
||||
name: disableThemePreview
|
||||
help: "关闭后,未包含主题管理权限的用户将无法通过参数预览未激活的主题"
|
||||
- $formkit: text
|
||||
label: "分类页路由前缀"
|
||||
value: "categories"
|
||||
|
|
Loading…
Reference in New Issue