diff --git a/src/main/java/run/halo/app/config/ExtensionConfiguration.java b/src/main/java/run/halo/app/config/ExtensionConfiguration.java index df4bd429b..6be89d6ea 100644 --- a/src/main/java/run/halo/app/config/ExtensionConfiguration.java +++ b/src/main/java/run/halo/app/config/ExtensionConfiguration.java @@ -1,6 +1,7 @@ package run.halo.app.config; import java.util.List; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.reactive.function.server.RouterFunction; @@ -25,6 +26,7 @@ import run.halo.app.extension.SchemeWatcherManager; import run.halo.app.extension.SchemeWatcherManager.SchemeWatcher; import run.halo.app.extension.controller.Controller; import run.halo.app.extension.controller.ControllerBuilder; +import run.halo.app.extension.controller.ControllerManager; import run.halo.app.extension.store.ExtensionStoreClient; import run.halo.app.plugin.HaloPluginManager; import run.halo.app.plugin.resources.JsBundleRuleProvider; @@ -54,36 +56,49 @@ public class ExtensionConfiguration { return new DefaultSchemeWatcherManager(); } - @Bean - Controller userController(ExtensionClient client) { - return new ControllerBuilder("user-controller", client) - .reconciler(new UserReconciler(client)) - .extension(new User()) - .build(); + @Configuration(proxyBeanMethods = false) + @ConditionalOnProperty(name = "halo.extension.controller.disabled", + havingValue = "false", + matchIfMissing = true) + static class ExtensionControllerConfiguration { + + @Bean + ControllerManager controllerManager() { + return new ControllerManager(); + } + + @Bean + Controller userController(ExtensionClient client) { + return new ControllerBuilder("user-controller", client) + .reconciler(new UserReconciler(client)) + .extension(new User()) + .build(); + } + + @Bean + Controller roleController(ExtensionClient client, RoleService roleService) { + return new ControllerBuilder("role-controller", client) + .reconciler(new RoleReconciler(client, roleService)) + .extension(new Role()) + .build(); + } + + @Bean + Controller roleBindingController(ExtensionClient client) { + return new ControllerBuilder("role-binding-controller", client) + .reconciler(new RoleBindingReconciler(client)) + .extension(new RoleBinding()) + .build(); + } + + @Bean + Controller pluginController(ExtensionClient client, HaloPluginManager haloPluginManager, + JsBundleRuleProvider jsBundleRule) { + return new ControllerBuilder("plugin-controller", client) + .reconciler(new PluginReconciler(client, haloPluginManager, jsBundleRule)) + .extension(new Plugin()) + .build(); + } } - @Bean - Controller roleController(ExtensionClient client, RoleService roleService) { - return new ControllerBuilder("role-controller", client) - .reconciler(new RoleReconciler(client, roleService)) - .extension(new Role()) - .build(); - } - - @Bean - Controller roleBindingController(ExtensionClient client) { - return new ControllerBuilder("role-binding-controller", client) - .reconciler(new RoleBindingReconciler(client)) - .extension(new RoleBinding()) - .build(); - } - - @Bean - Controller pluginController(ExtensionClient client, HaloPluginManager haloPluginManager, - JsBundleRuleProvider jsBundleRule) { - return new ControllerBuilder("plugin-controller", client) - .reconciler(new PluginReconciler(client, haloPluginManager, jsBundleRule)) - .extension(new Plugin()) - .build(); - } } diff --git a/src/main/java/run/halo/app/config/WebServerSecurityConfig.java b/src/main/java/run/halo/app/config/WebServerSecurityConfig.java index 0ec8a8223..025d7b15e 100644 --- a/src/main/java/run/halo/app/config/WebServerSecurityConfig.java +++ b/src/main/java/run/halo/app/config/WebServerSecurityConfig.java @@ -9,6 +9,7 @@ import com.nimbusds.jose.jwk.RSAKey; import com.nimbusds.jose.jwk.source.ImmutableJWKSet; import java.util.Arrays; import java.util.List; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Bean; import org.springframework.core.Ordered; import org.springframework.core.annotation.Order; @@ -33,8 +34,11 @@ import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; import org.springframework.web.reactive.function.server.ServerResponse; import run.halo.app.core.extension.service.RoleService; import run.halo.app.core.extension.service.UserService; +import run.halo.app.extension.ExtensionClient; +import run.halo.app.infra.properties.HaloProperties; import run.halo.app.infra.properties.JwtProperties; import run.halo.app.security.DefaultUserDetailService; +import run.halo.app.security.SuperAdminInitializer; import run.halo.app.security.authentication.jwt.LoginAuthenticationFilter; import run.halo.app.security.authentication.jwt.LoginAuthenticationManager; import run.halo.app.security.authorization.RequestInfoAuthorizationManager; @@ -143,4 +147,13 @@ public class WebServerSecurityConfig { return new NimbusJwtEncoder(jwks); } + @Bean + @ConditionalOnProperty(name = "halo.security.initializer.disabled", + havingValue = "false", + matchIfMissing = true) + SuperAdminInitializer superAdminInitializer(ExtensionClient client, HaloProperties halo) { + return new SuperAdminInitializer(client, + passwordEncoder(), + halo.getSecurity().getInitializer()); + } } diff --git a/src/main/java/run/halo/app/extension/controller/ControllerManager.java b/src/main/java/run/halo/app/extension/controller/ControllerManager.java index 776cd4600..ee6f42523 100644 --- a/src/main/java/run/halo/app/extension/controller/ControllerManager.java +++ b/src/main/java/run/halo/app/extension/controller/ControllerManager.java @@ -1,22 +1,18 @@ package run.halo.app.extension.controller; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.BeansException; import org.springframework.beans.factory.DisposableBean; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationListener; -import org.springframework.stereotype.Component; @Slf4j -@Component public class ControllerManager implements ApplicationListener, - DisposableBean { + ApplicationContextAware, DisposableBean { - private final ApplicationContext applicationContext; - - public ControllerManager(ApplicationContext applicationContext) { - this.applicationContext = applicationContext; - } + private ApplicationContext applicationContext; @Override public void onApplicationEvent(ApplicationReadyEvent event) { @@ -39,4 +35,8 @@ public class ControllerManager implements ApplicationListener initialExtensionLocations; + private Set initialExtensionLocations = new HashSet<>(); + + private final ExtensionProperties extension = new ExtensionProperties(); + + private final SecurityProperties security = new SecurityProperties(); + } diff --git a/src/main/java/run/halo/app/infra/properties/SecurityProperties.java b/src/main/java/run/halo/app/infra/properties/SecurityProperties.java new file mode 100644 index 000000000..04828b82d --- /dev/null +++ b/src/main/java/run/halo/app/infra/properties/SecurityProperties.java @@ -0,0 +1,21 @@ +package run.halo.app.infra.properties; + +import lombok.Data; + +@Data +public class SecurityProperties { + + private final Initializer initializer = new Initializer(); + + @Data + public static class Initializer { + + private boolean disabled; + + private String superAdminUsername = "admin"; + + private String superAdminPassword; + + } + +} diff --git a/src/main/java/run/halo/app/security/SuperAdminInitializer.java b/src/main/java/run/halo/app/security/SuperAdminInitializer.java index fd462a65a..80043b5d8 100644 --- a/src/main/java/run/halo/app/security/SuperAdminInitializer.java +++ b/src/main/java/run/halo/app/security/SuperAdminInitializer.java @@ -9,7 +9,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationListener; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; +import org.springframework.util.StringUtils; import run.halo.app.core.extension.Role; import run.halo.app.core.extension.Role.PolicyRule; import run.halo.app.core.extension.RoleBinding; @@ -19,22 +19,28 @@ import run.halo.app.core.extension.User; import run.halo.app.core.extension.User.UserSpec; import run.halo.app.extension.ExtensionClient; import run.halo.app.extension.Metadata; +import run.halo.app.infra.properties.SecurityProperties.Initializer; @Slf4j -@Component public class SuperAdminInitializer implements ApplicationListener { + private static final String SUPER_ROLE_NAME = "super-role"; + private final ExtensionClient client; private final PasswordEncoder passwordEncoder; - public SuperAdminInitializer(ExtensionClient client, PasswordEncoder passwordEncoder) { + private final Initializer initializer; + + public SuperAdminInitializer(ExtensionClient client, PasswordEncoder passwordEncoder, + Initializer initializer) { this.client = client; this.passwordEncoder = passwordEncoder; + this.initializer = initializer; } @Override public void onApplicationEvent(ApplicationReadyEvent event) { - client.fetch(User.class, "admin").ifPresentOrElse(user -> { + client.fetch(User.class, initializer.getSuperAdminUsername()).ifPresentOrElse(user -> { // do nothing if admin has been initialized }, () -> { var admin = createAdmin(); @@ -48,7 +54,9 @@ public class SuperAdminInitializer implements ApplicationListener annotations = new HashMap<>(); annotations.put(Role.UI_PERMISSIONS_ANNO, "[\"*\"]"); metadata.setAnnotations(annotations); @@ -89,7 +97,7 @@ public class SuperAdminInitializer implements ApplicationListener { if (extension instanceof User user) { - return "admin".equals(user.getMetadata().getName()); + return "fake-admin".equals(user.getMetadata().getName()) + && encoder.matches("fake-password", user.getSpec().getPassword()); } return false; })); @@ -44,7 +51,7 @@ class SuperAdminInitializerTest { })); verify(client, times(1)).create(argThat(extension -> { if (extension instanceof RoleBinding roleBinding) { - return "admin-super-role-binding".equals(roleBinding.getMetadata().getName()); + return "fake-admin-super-role-binding".equals(roleBinding.getMetadata().getName()); } return false; })); diff --git a/src/test/resources/application.yaml b/src/test/resources/application.yaml index 2544cacd0..84350717c 100644 --- a/src/test/resources/application.yaml +++ b/src/test/resources/application.yaml @@ -19,10 +19,15 @@ spring: halo: security: + initializer: + disabled: true oauth2: jwt: public-key-location: classpath:app.pub private-key-location: classpath:app.key + extension: + controller: + disabled: true springdoc: api-docs: