org.springframework
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CrshAutoConfiguration.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CrshAutoConfiguration.java
new file mode 100644
index 00000000000..61c24ce0d37
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/autoconfigure/CrshAutoConfiguration.java
@@ -0,0 +1,462 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.autoconfigure;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.annotation.PostConstruct;
+import javax.annotation.PreDestroy;
+
+import org.crsh.auth.AuthenticationPlugin;
+import org.crsh.plugin.CRaSHPlugin;
+import org.crsh.plugin.PluginContext;
+import org.crsh.plugin.PluginDiscovery;
+import org.crsh.plugin.PluginLifeCycle;
+import org.crsh.plugin.PropertyDescriptor;
+import org.crsh.plugin.ServiceLoaderDiscovery;
+import org.crsh.vfs.FS;
+import org.crsh.vfs.spi.AbstractFSDriver;
+import org.springframework.beans.factory.ListableBeanFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.actuate.properties.CrshProperties;
+import org.springframework.boot.actuate.properties.CrshProperties.AuthenticationProperties;
+import org.springframework.boot.actuate.properties.CrshProperties.JaasAuthenticationProperties;
+import org.springframework.boot.actuate.properties.CrshProperties.KeyAuthenticationProperties;
+import org.springframework.boot.actuate.properties.CrshProperties.SimpleAuthenticationProperties;
+import org.springframework.boot.actuate.properties.CrshProperties.SpringAuthenticationProperties;
+import org.springframework.boot.autoconfigure.AutoConfigureAfter;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.SpringVersion;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.ResourcePatternResolver;
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.access.SecurityConfig;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+
+/**
+ * {@link EnableAutoConfiguration Auto-configuration} for embedding an extensible shell into a Spring
+ * Boot enabled application. By default a SSH daemon is started on port 2000 with a default username
+ * user and password (default password is logged during application startup).
+ *
+ *
+ * This configuration will auto detect the existence of a Spring Security {@link AuthenticationManager}
+ * and will delegate authentication requests for shell access to this detected instance.
+ *
+ *
+ * To add customizations to the shell simply define beans of type {@link CRaSHPlugin} in the
+ * application context. Those beans will get auto detected during startup and registered with the
+ * underlying shell infrastructure.
+ *
+ *
+ * Additional shell commands can be implemented using the guide and documentation at
+ * crashub.org. By default Boot will search for commands using
+ * the following classpath scanning pattern classpath*:/commands/**. To add different
+ * locations or override the default use shell.command_path_patterns in your application
+ * configuration.
+ *
+ * @author Christian Dupuis
+ */
+@Configuration
+@ConditionalOnClass({ PluginLifeCycle.class })
+@EnableConfigurationProperties({ CrshProperties.class })
+@AutoConfigureAfter(SecurityAutoConfiguration.class)
+public class CrshAutoConfiguration {
+
+ @Autowired
+ private CrshProperties properties;
+
+
+ @Bean
+ @ConditionalOnExpression("#{environment['shell.auth'] == 'jaas'}")
+ @ConditionalOnMissingBean({ AuthenticationProperties.class })
+ public AuthenticationProperties jaasAuthenticationProperties() {
+ return new JaasAuthenticationProperties();
+ }
+
+ @Bean
+ @ConditionalOnExpression("#{environment['shell.auth'] == 'key'}")
+ @ConditionalOnMissingBean({ AuthenticationProperties.class })
+ public AuthenticationProperties keyAuthenticationProperties() {
+ return new KeyAuthenticationProperties();
+ }
+
+ @Bean
+ @ConditionalOnExpression("#{environment['shell.auth'] == 'simple'}")
+ @ConditionalOnMissingBean({ AuthenticationProperties.class })
+ public AuthenticationProperties simpleAuthenticationProperties() {
+ return new SimpleAuthenticationProperties();
+ }
+
+ @Bean
+ @ConditionalOnExpression("#{environment['shell.auth'] == 'spring'}")
+ @ConditionalOnMissingBean({ AuthenticationProperties.class })
+ public AuthenticationProperties SpringAuthenticationProperties() {
+ return new SpringAuthenticationProperties();
+ }
+
+ @Bean
+ @ConditionalOnBean({ AuthenticationManager.class })
+ public CRaSHPlugin> shellAuthenticationManager() {
+ return new AuthenticationManagerAdapter();
+ }
+
+ @Bean
+ @ConditionalOnMissingBean({ PluginLifeCycle.class })
+ public PluginLifeCycle shellBootstrap() {
+ CrshBootstrap bs = new CrshBootstrap();
+ bs.setConfig(properties.mergeProperties(new Properties()));
+ return bs;
+ }
+
+
+ public static class CrshBootstrap extends PluginLifeCycle {
+
+ @Autowired
+ private ListableBeanFactory beanFactory;
+
+ @Autowired
+ private CrshProperties properties;
+
+ @Autowired
+ private ResourcePatternResolver resourceLoader;
+
+
+ @PreDestroy
+ public void destroy() {
+ stop();
+ }
+
+ @PostConstruct
+ public void init() throws Exception {
+ FS commandFileSystem = createFileSystem(properties.getCommandPathPatterns());
+ FS confFileSystem = createFileSystem(properties.getConfigPathPatterns());
+
+ PluginDiscovery discovery = new BeanFactoryFilteringPluginDiscovery(resourceLoader.getClassLoader(),
+ beanFactory, properties.getDisabledPlugins());
+
+ PluginContext context = new PluginContext(discovery, createPluginContextAttributes(),
+ commandFileSystem, confFileSystem, resourceLoader.getClassLoader());
+
+ context.refresh();
+ start(context);
+ }
+
+
+ protected FS createFileSystem(String[] pathPatterns) throws IOException, URISyntaxException {
+ Assert.notNull(pathPatterns);
+ FS cmdFS = new FS();
+ for (String pathPattern : pathPatterns) {
+ cmdFS.mount(new SimpleFileSystemDriver(new DirectoryHandle(pathPattern, resourceLoader)));
+ }
+ return cmdFS;
+ }
+
+ protected Map createPluginContextAttributes() {
+ Map attributes = new HashMap();
+ String bootVersion = CrshAutoConfiguration.class.getPackage().getImplementationVersion();
+ if (bootVersion != null) {
+ attributes.put("spring.boot.version", bootVersion);
+ }
+ attributes.put("spring.version", SpringVersion.getVersion());
+ if (beanFactory != null) {
+ attributes.put("spring.beanfactory", beanFactory);
+ }
+ return attributes;
+ }
+
+ }
+
+
+ @SuppressWarnings("rawtypes")
+ private static class AuthenticationManagerAdapter extends CRaSHPlugin implements
+ AuthenticationPlugin {
+
+ private static final PropertyDescriptor ROLES = PropertyDescriptor.create(
+ "auth.spring.roles", "ADMIN", "Comma separated list of roles required to access the shell");
+
+
+ @Autowired(required=false)
+ private AccessDecisionManager accessDecisionManager;
+
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
+ private String[] roles = new String[] { "ROLE_ADMIN" };
+
+
+ @Override
+ public boolean authenticate(String username, String password) throws Exception {
+ // Authenticate first to make credentials are valid
+ Authentication token = new UsernamePasswordAuthenticationToken(username, password);
+ try {
+ token = authenticationManager.authenticate(token);
+ }
+ catch (AuthenticationException ae) {
+ return false;
+ }
+
+ // Test access rights if a Spring Security AccessDecisionManager is installed
+ if (accessDecisionManager != null && token.isAuthenticated() && roles != null) {
+ try {
+ accessDecisionManager.decide(token, this, SecurityConfig.createList(roles));
+ }
+ catch (AccessDeniedException e) {
+ return false;
+ }
+ }
+ return token.isAuthenticated();
+ }
+
+ @Override
+ public Class getCredentialType() {
+ return String.class;
+ }
+
+ @Override
+ public AuthenticationPlugin getImplementation() {
+ return this;
+ }
+
+ @Override
+ public String getName() {
+ return "spring";
+ }
+
+ @Override
+ public void init() {
+ String rolesPropertyValue = getContext().getProperty(ROLES);
+ if (rolesPropertyValue != null) {
+ this.roles = StringUtils.commaDelimitedListToStringArray(rolesPropertyValue);
+ }
+ }
+
+
+ @Override
+ protected Iterable> createConfigurationCapabilities() {
+ return Arrays.>asList(ROLES);
+ }
+
+ }
+
+
+ private static class BeanFactoryFilteringPluginDiscovery extends ServiceLoaderDiscovery {
+
+ private ListableBeanFactory beanFactory;
+
+ private String[] disabledPlugins;
+
+
+ public BeanFactoryFilteringPluginDiscovery(ClassLoader classLoader, ListableBeanFactory beanFactory,
+ String[] disabledPlugins)
+ throws NullPointerException {
+ super(classLoader);
+ this.beanFactory = beanFactory;
+ this.disabledPlugins = disabledPlugins;
+ }
+
+
+ @Override
+ @SuppressWarnings("rawtypes")
+ public Iterable> getPlugins() {
+ List> plugins = new ArrayList>();
+
+ for (CRaSHPlugin> p : super.getPlugins()) {
+ if (!shouldFilter(p)) {
+ plugins.add(p);
+ }
+ }
+
+ Collection springPlugins = beanFactory.getBeansOfType(CRaSHPlugin.class).values();
+ for (CRaSHPlugin> p : springPlugins) {
+ if (!shouldFilter(p)) {
+ plugins.add(p);
+ }
+ }
+
+ return plugins;
+ }
+
+
+ @SuppressWarnings("rawtypes")
+ protected boolean shouldFilter(CRaSHPlugin> plugin) {
+ Assert.notNull(plugin);
+
+ Set classes = ClassUtils.getAllInterfacesAsSet(plugin);
+ classes.add(plugin.getClass());
+
+ for (Class> clazz : classes) {
+ if (disabledPlugins != null && disabledPlugins.length > 0) {
+ for (String disabledPlugin : disabledPlugins) {
+ if (ClassUtils.getShortName(clazz).equalsIgnoreCase(disabledPlugin)
+ || ClassUtils.getQualifiedName(clazz).equalsIgnoreCase(disabledPlugin)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ }
+
+
+ private static class SimpleFileSystemDriver extends AbstractFSDriver {
+
+ private ResourceHandle root;
+
+
+ public SimpleFileSystemDriver(ResourceHandle handle) {
+ this.root = handle;
+ }
+
+
+ @Override
+ public Iterable children(ResourceHandle handle) throws IOException {
+ if (handle instanceof DirectoryHandle) {
+ return ((DirectoryHandle) handle).members();
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public long getLastModified(ResourceHandle handle) throws IOException {
+ if (handle instanceof FileHandle) {
+ return ((FileHandle) handle).getLastModified();
+ }
+ return -1;
+ }
+
+ @Override
+ public boolean isDir(ResourceHandle handle) throws IOException {
+ return handle instanceof DirectoryHandle;
+ }
+
+ @Override
+ public String name(ResourceHandle handle) throws IOException {
+ return handle.getName();
+ }
+
+ @Override
+ public Iterator open(ResourceHandle handle) throws IOException {
+ if (handle instanceof FileHandle) {
+ return Collections.singletonList(((FileHandle) handle).openStream()).iterator();
+ }
+ return Collections.emptyList().iterator();
+ }
+
+ @Override
+ public ResourceHandle root() throws IOException {
+ return root;
+ }
+
+ }
+
+
+ private static class DirectoryHandle extends ResourceHandle {
+
+ private ResourcePatternResolver resourceLoader;
+
+
+ public DirectoryHandle(String name, ResourcePatternResolver resourceLoader) {
+ super(name);
+ this.resourceLoader = resourceLoader;
+ }
+
+
+ public List members() throws IOException {
+ Resource[] resources = resourceLoader.getResources(getName());
+ List files = new ArrayList();
+ for (Resource resource : resources) {
+ if (!resource.getURL().getPath().endsWith("/")) {
+ files.add(new FileHandle(resource.getFilename(), resource));
+ }
+ }
+ return files;
+ }
+
+ }
+
+
+ private static class FileHandle extends ResourceHandle {
+
+ private Resource resource;
+
+
+ public FileHandle(String name, Resource resource) {
+ super(name);
+ this.resource = resource;
+ }
+
+
+ public InputStream openStream() throws IOException {
+ return this.resource.getInputStream();
+ }
+
+ public long getLastModified() {
+ try {
+ return this.resource.lastModified();
+ }
+ catch (IOException e) {}
+ return -1;
+ }
+
+ }
+
+
+ private abstract static class ResourceHandle {
+
+ private String name;
+
+
+ public ResourceHandle(String name) {
+ this.name = name;
+ }
+
+
+ public String getName() {
+ return name;
+ }
+
+ }
+
+}
diff --git a/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/CrshProperties.java b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/CrshProperties.java
new file mode 100644
index 00000000000..cc0c8f2e6ff
--- /dev/null
+++ b/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/properties/CrshProperties.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.properties;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+import java.util.UUID;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
+
+/**
+ * Configuration properties for the shell subsystem.
+ *
+ * @author Christian Dupuis
+ */
+@ConfigurationProperties(name = "shell", ignoreUnknownFields = true)
+public class CrshProperties {
+
+ protected static final String CRASH_AUTH = "crash.auth";
+ protected static final String CRASH_AUTH_JAAS_DOMAIN = "crash.auth.jaas.domain";
+ protected static final String CRASH_AUTH_KEY_PATH = "crash.auth.key.path";
+ protected static final String CRASH_AUTH_SIMPLE_PASSWORD = "crash.auth.simple.password";
+ protected static final String CRASH_AUTH_SIMPLE_USERNAME = "crash.auth.simple.username";
+ protected static final String CRASH_AUTH_SPRING_ROLES = "crash.auth.spring.roles";
+ protected static final String CRASH_SSH_KEYPATH = "crash.ssh.keypath";
+ protected static final String CRASH_SSH_PORT = "crash.ssh.port";
+ protected static final String CRASH_TELNET_PORT = "crash.telnet.port";
+ protected static final String CRASH_VFS_REFRESH_PERIOD = "crash.vfs.refresh_period";
+
+ private String auth = "simple";
+
+ @Autowired(required = false)
+ private AuthenticationProperties authenticationProperties;
+
+ private int commandRefreshInterval = -1;
+
+ private String[] commandPathPatterns = new String[] { "classpath*:/commands/**",
+ "classpath*:/crash/commands/**" };
+
+ private String[] configPathPatterns = new String[] { "classpath*:/crash/*" };
+
+ private String[] disabledPlugins = new String[0];
+
+ private Ssh ssh = new Ssh();
+
+ private Telnet telnet = new Telnet();
+
+
+ public String getAuth() {
+ return this.auth;
+ }
+
+ public AuthenticationProperties getAuthenticationProperties() {
+ return this.authenticationProperties;
+ }
+
+ public int getCommandRefreshInterval() {
+ return this.commandRefreshInterval;
+ }
+
+ public String[] getCommandPathPatterns() {
+ return this.commandPathPatterns;
+ }
+
+ public String[] getConfigPathPatterns() {
+ return this.configPathPatterns;
+ }
+
+ public String[] getDisabledPlugins() {
+ return this.disabledPlugins;
+ }
+
+ public Ssh getSsh() {
+ return this.ssh;
+ }
+
+ public Telnet getTelnet() {
+ return this.telnet;
+ }
+
+ public Properties mergeProperties(Properties properties) {
+ properties = ssh.mergeProperties(properties);
+ properties = telnet.mergeProperties(properties);
+
+ properties.put(CRASH_AUTH, auth);
+ if (authenticationProperties != null) {
+ properties = authenticationProperties.mergeProperties(properties);
+ }
+
+ if (this.commandRefreshInterval > 0) {
+ properties.put(CRASH_VFS_REFRESH_PERIOD, String.valueOf(this.commandRefreshInterval));
+ }
+
+ // special handling for disabling Ssh and Telnet support
+ List dp = new ArrayList(Arrays.asList(this.disabledPlugins));
+ if (!ssh.isEnabled()) {
+ dp.add("org.crsh.ssh.SSHPlugin");
+ }
+ if (!telnet.isEnabled()) {
+ dp.add("org.crsh.telnet.TelnetPlugin");
+ }
+ this.disabledPlugins = dp.toArray(new String[dp.size()]);
+
+ return properties;
+ }
+
+ public void setAuth(String auth) {
+ Assert.hasLength(auth);
+ this.auth = auth;
+ }
+
+ public void setAuthenticationProperties(AuthenticationProperties authenticationProperties) {
+ Assert.notNull(authenticationProperties);
+ this.authenticationProperties = authenticationProperties;
+ }
+
+ public void setCommandRefreshInterval(int commandRefreshInterval) {
+ this.commandRefreshInterval = commandRefreshInterval;
+ }
+
+ public void setCommandPathPatterns(String[] commandPathPatterns) {
+ Assert.notEmpty(commandPathPatterns);
+ this.commandPathPatterns = commandPathPatterns;
+ }
+
+ public void setConfigPathPatterns(String[] configPathPatterns) {
+ Assert.notEmpty(configPathPatterns);
+ this.configPathPatterns = configPathPatterns;
+ }
+
+ public void setDisabledPlugins(String[] disabledPlugins) {
+ Assert.notEmpty(disabledPlugins);
+ this.disabledPlugins = disabledPlugins;
+ }
+
+ public void setSsh(Ssh ssh) {
+ Assert.notNull(ssh);
+ this.ssh = ssh;
+ }
+
+ public void setTelnet(Telnet telnet) {
+ Assert.notNull(telnet);
+ this.telnet = telnet;
+ }
+
+
+ public interface AuthenticationProperties extends PropertiesProvider {
+ }
+
+
+ @ConfigurationProperties(name = "shell.auth.jaas", ignoreUnknownFields = false)
+ public static class JaasAuthenticationProperties implements AuthenticationProperties {
+
+ private String domain = "my-domain";
+
+
+ @Override
+ public Properties mergeProperties(Properties properties) {
+ properties.put(CRASH_AUTH_JAAS_DOMAIN, this.domain);
+ return properties;
+ }
+
+ public void setDomain(String domain) {
+ Assert.hasText(domain);
+ this.domain = domain;
+ }
+
+ }
+
+
+ @ConfigurationProperties(name = "shell.auth.key", ignoreUnknownFields = false)
+ public static class KeyAuthenticationProperties implements AuthenticationProperties {
+
+ private String path;
+
+
+ @Override
+ public Properties mergeProperties(Properties properties) {
+ if (this.path != null) {
+ properties.put(CRASH_AUTH_KEY_PATH, this.path);
+ }
+ return properties;
+ }
+
+ public void setPath(String path) {
+ Assert.hasText(path);
+ this.path = path;
+ }
+
+ }
+
+
+ public interface PropertiesProvider {
+ Properties mergeProperties(Properties properties);
+ }
+
+
+ @ConfigurationProperties(name = "shell.auth.simple", ignoreUnknownFields = false)
+ public static class SimpleAuthenticationProperties implements AuthenticationProperties {
+
+ private static Log logger = LogFactory.getLog(SimpleAuthenticationProperties.class);
+
+
+ private String username = "user";
+
+ private String password = UUID.randomUUID().toString();
+
+ private boolean defaultPassword = true;
+
+
+ public boolean isDefaultPassword() {
+ return this.defaultPassword;
+ }
+
+ @Override
+ public Properties mergeProperties(Properties properties) {
+ properties.put(CRASH_AUTH_SIMPLE_USERNAME, this.username);
+ properties.put(CRASH_AUTH_SIMPLE_PASSWORD, this.password);
+ if (this.defaultPassword) {
+ logger.info("Using default password for shell access: " + this.password);
+ }
+ return properties;
+ }
+
+ public void setPassword(String password) {
+ if (password.startsWith("${") && password.endsWith("}") || !StringUtils.hasLength(password)) {
+ return;
+ }
+ this.password = password;
+ this.defaultPassword = false;
+ }
+
+ public void setUsername(String username) {
+ Assert.hasLength(username);
+ this.username = username;
+ }
+
+ }
+
+
+ @ConfigurationProperties(name = "shell.auth.spring", ignoreUnknownFields = false)
+ public static class SpringAuthenticationProperties implements AuthenticationProperties {
+
+ private String[] roles = new String[] { "ROLE_ADMIN" };
+
+
+ @Override
+ public Properties mergeProperties(Properties properties) {
+ if (this.roles != null) {
+ properties.put(CRASH_AUTH_SPRING_ROLES, StringUtils.arrayToCommaDelimitedString(this.roles));
+ }
+ return properties;
+ }
+
+ public void setRoles(String[] roles) {
+ Assert.notNull(roles);
+ this.roles = roles;
+ }
+
+ }
+
+
+ public static class Ssh implements PropertiesProvider {
+
+ private boolean enabled = true;
+
+ private String keyPath = null;
+
+ private String port = "2000";
+
+
+ public boolean isEnabled() {
+ return this.enabled;
+ }
+
+ @Override
+ public Properties mergeProperties(Properties properties) {
+ if (this.enabled) {
+ properties.put(CRASH_SSH_PORT, this.port);
+ if (this.keyPath != null) {
+ properties.put(CRASH_SSH_KEYPATH, this.keyPath);
+ }
+ }
+ return properties;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public void setKeyPath(String keyPath) {
+ Assert.hasText(keyPath);
+ this.keyPath = keyPath;
+ }
+
+ public void setPort(Integer port) {
+ Assert.notNull(port);
+ this.port = port.toString();
+ }
+
+ }
+
+
+ public static class Telnet implements PropertiesProvider {
+
+ private boolean enabled = false;
+
+ private String port = "5000";
+
+
+ public boolean isEnabled() {
+ return this.enabled;
+ }
+
+ @Override
+ public Properties mergeProperties(Properties properties) {
+ if (this.enabled) {
+ properties.put(CRASH_TELNET_PORT, this.port);
+ }
+ return properties;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public void setPort(Integer port) {
+ Assert.notNull(port);
+ this.port = port.toString();
+ }
+
+ }
+
+}
diff --git a/spring-boot-actuator/src/main/resources/META-INF/spring.factories b/spring-boot-actuator/src/main/resources/META-INF/spring.factories
index 55cabc4f408..89736607c7d 100644
--- a/spring-boot-actuator/src/main/resources/META-INF/spring.factories
+++ b/spring-boot-actuator/src/main/resources/META-INF/spring.factories
@@ -1,5 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.actuate.autoconfigure.AuditAutoConfiguration,\
+org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.EndpointAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.EndpointWebMvcAutoConfiguration,\
org.springframework.boot.actuate.autoconfigure.ErrorMvcAutoConfiguration,\
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CrshAutoConfigurationTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CrshAutoConfigurationTests.java
new file mode 100644
index 00000000000..1843535e2f2
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/autoconfigure/CrshAutoConfigurationTests.java
@@ -0,0 +1,333 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.autoconfigure;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.crsh.auth.AuthenticationPlugin;
+import org.crsh.auth.JaasAuthenticationPlugin;
+import org.crsh.lang.groovy.GroovyREPL;
+import org.crsh.plugin.PluginContext;
+import org.crsh.plugin.PluginLifeCycle;
+import org.crsh.plugin.ResourceKind;
+import org.crsh.processor.term.ProcessorIOHandler;
+import org.crsh.vfs.Resource;
+import org.junit.After;
+import org.junit.Test;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.mock.env.MockEnvironment;
+import org.springframework.mock.web.MockServletContext;
+import org.springframework.security.access.AccessDecisionManager;
+import org.springframework.security.access.AccessDecisionVoter;
+import org.springframework.security.access.vote.RoleVoter;
+import org.springframework.security.access.vote.UnanimousBased;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+
+/**
+ * Tests for {@link CrshAutoConfiguration}.
+ *
+ * @author Christian Dupuis
+ */
+@SuppressWarnings({ "rawtypes", "unchecked" })
+public class CrshAutoConfigurationTests {
+
+ private AnnotationConfigWebApplicationContext context;
+
+ @After
+ public void tearDown() {
+ if (this.context != null) {
+ this.context.close();
+ this.context = null;
+ }
+ }
+
+ @Test
+ public void testDisabledPlugins() throws Exception {
+ MockEnvironment env = new MockEnvironment();
+ env.setProperty("shell.disabled_plugins", "GroovyREPL, termIOHandler, org.crsh.auth.AuthenticationPlugin");
+ this.context = new AnnotationConfigWebApplicationContext();
+ this.context.setEnvironment(env);
+ this.context.register(CrshAutoConfiguration.class);
+ this.context.refresh();
+
+ PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
+ assertNotNull(lifeCycle);
+
+ assertNull(lifeCycle.getContext().getPlugin(GroovyREPL.class));
+ assertNull(lifeCycle.getContext().getPlugin(ProcessorIOHandler.class));
+ assertNull(lifeCycle.getContext().getPlugin(JaasAuthenticationPlugin.class));
+ }
+
+ @Test
+ public void testAttributes() throws Exception {
+ this.context = new AnnotationConfigWebApplicationContext();
+ this.context.register(CrshAutoConfiguration.class);
+ this.context.refresh();
+
+ PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
+
+ Map attributes = lifeCycle.getContext().getAttributes();
+ assertTrue(attributes.containsKey("spring.version"));
+ assertTrue(attributes.containsKey("spring.beanfactory"));
+ assertEquals(this.context.getBeanFactory(), attributes.get("spring.beanfactory"));
+ }
+
+ @Test
+ public void testSshConfiguration() {
+ MockEnvironment env = new MockEnvironment();
+ env.setProperty("shell.ssh.enabled", "true");
+ env.setProperty("shell.ssh.port", "3333");
+ this.context = new AnnotationConfigWebApplicationContext();
+ this.context.setEnvironment(env);
+ this.context.register(CrshAutoConfiguration.class);
+ this.context.refresh();
+
+ PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
+
+ assertEquals(lifeCycle.getConfig().getProperty("crash.ssh.port"), "3333");
+ }
+
+ @Test
+ public void testSshConfigurationWithKeyPath() {
+ MockEnvironment env = new MockEnvironment();
+ env.setProperty("shell.ssh.enabled", "true");
+ env.setProperty("shell.ssh.key_path", "~/.ssh/id.pem");
+ this.context = new AnnotationConfigWebApplicationContext();
+ this.context.setEnvironment(env);
+ this.context.register(CrshAutoConfiguration.class);
+ this.context.refresh();
+
+ PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
+
+ assertEquals(lifeCycle.getConfig().getProperty("crash.ssh.keypath"), "~/.ssh/id.pem");
+ }
+
+ @Test
+ public void testCommandResolution() {
+ this.context = new AnnotationConfigWebApplicationContext();
+ this.context.register(CrshAutoConfiguration.class);
+ this.context.refresh();
+
+ PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
+
+ int count = 0;
+ Iterator resources = lifeCycle.getContext().loadResources("login", ResourceKind.LIFECYCLE).iterator();
+ while (resources.hasNext()) {
+ count++;
+ resources.next();
+ }
+ assertEquals(1, count);
+
+ count = 0;
+ resources = lifeCycle.getContext().loadResources("help.java", ResourceKind.COMMAND).iterator();
+ while (resources.hasNext()) {
+ count++;
+ resources.next();
+ }
+ assertEquals(1, count);
+ }
+
+ @Test
+ public void testAuthenticationProvidersAreInstalled() {
+ this.context = new AnnotationConfigWebApplicationContext();
+ this.context.setServletContext(new MockServletContext());
+ this.context.register(SecurityConfiguration.class);
+ this.context.register(CrshAutoConfiguration.class);
+ this.context.refresh();
+
+ PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
+ PluginContext pluginContext = lifeCycle.getContext();
+
+ int count = 0;
+ Iterator plugins = pluginContext.getPlugins(AuthenticationPlugin.class).iterator();
+ while (plugins.hasNext()) {
+ count++;
+ plugins.next();
+ }
+ assertEquals(3, count);
+ }
+
+ @Test
+ public void testJaasAuthenticationProvider() {
+ MockEnvironment env = new MockEnvironment();
+ env.setProperty("shell.auth", "jaas");
+ env.setProperty("shell.auth.jaas.domain", "my-test-domain");
+ this.context = new AnnotationConfigWebApplicationContext();
+ this.context.setEnvironment(env);
+ this.context.setServletContext(new MockServletContext());
+ this.context.register(SecurityConfiguration.class);
+ this.context.register(CrshAutoConfiguration.class);
+ this.context.refresh();
+
+ PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
+ assertEquals(lifeCycle.getConfig().get("crash.auth"), "jaas");
+ assertEquals(lifeCycle.getConfig().get("crash.auth.jaas.domain"), "my-test-domain");
+ }
+
+ @Test
+ public void testKeyAuthenticationProvider() {
+ MockEnvironment env = new MockEnvironment();
+ env.setProperty("shell.auth", "key");
+ env.setProperty("shell.auth.key.path", "~/test.pem");
+ this.context = new AnnotationConfigWebApplicationContext();
+ this.context.setEnvironment(env);
+ this.context.setServletContext(new MockServletContext());
+ this.context.register(SecurityConfiguration.class);
+ this.context.register(CrshAutoConfiguration.class);
+ this.context.refresh();
+
+ PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
+ assertEquals(lifeCycle.getConfig().get("crash.auth"), "key");
+ assertEquals(lifeCycle.getConfig().get("crash.auth.key.path"), "~/test.pem");
+ }
+
+ @Test
+ public void testSimpleAuthenticationProvider() {
+ MockEnvironment env = new MockEnvironment();
+ env.setProperty("shell.auth", "simple");
+ env.setProperty("shell.auth.simple.username", "user");
+ env.setProperty("shell.auth.simple.password", "password");
+ this.context = new AnnotationConfigWebApplicationContext();
+ this.context.setEnvironment(env);
+ this.context.setServletContext(new MockServletContext());
+ this.context.register(SecurityConfiguration.class);
+ this.context.register(CrshAutoConfiguration.class);
+ this.context.refresh();
+
+ PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
+ assertEquals(lifeCycle.getConfig().get("crash.auth"), "simple");
+
+ AuthenticationPlugin authenticationPlugin = null;
+ String authentication = lifeCycle.getConfig().getProperty("crash.auth");
+ assertNotNull(authentication);
+ for (AuthenticationPlugin plugin : lifeCycle.getContext().getPlugins(AuthenticationPlugin.class)) {
+ if (authentication.equals(plugin.getName())) {
+ authenticationPlugin = plugin;
+ break;
+ }
+ }
+ assertNotNull(authenticationPlugin);
+ try {
+ assertTrue(authenticationPlugin.authenticate("user", "password"));
+ }
+ catch (Exception e) {
+ fail();
+ }
+
+ try {
+ assertFalse(authenticationPlugin.authenticate(UUID.randomUUID().toString(),
+ "password"));
+ }
+ catch (Exception e) {
+ fail();
+ }
+ }
+
+ @Test
+ public void testSpringAuthenticationProvider() {
+ MockEnvironment env = new MockEnvironment();
+ env.setProperty("shell.auth", "spring");
+ this.context = new AnnotationConfigWebApplicationContext();
+ this.context.setEnvironment(env);
+ this.context.setServletContext(new MockServletContext());
+ this.context.register(SecurityConfiguration.class);
+ this.context.register(CrshAutoConfiguration.class);
+ this.context.refresh();
+
+ PluginLifeCycle lifeCycle = this.context.getBean(PluginLifeCycle.class);
+
+ AuthenticationPlugin authenticationPlugin = null;
+ String authentication = lifeCycle.getConfig().getProperty("crash.auth");
+ assertNotNull(authentication);
+ for (AuthenticationPlugin plugin : lifeCycle.getContext().getPlugins(AuthenticationPlugin.class)) {
+ if (authentication.equals(plugin.getName())) {
+ authenticationPlugin = plugin;
+ break;
+ }
+ }
+ assertNotNull(authenticationPlugin);
+ try {
+ assertTrue(authenticationPlugin.authenticate(SecurityConfiguration.USERNAME,
+ SecurityConfiguration.PASSWORD));
+ }
+ catch (Exception e) {
+ fail();
+ }
+
+ try {
+ assertFalse(authenticationPlugin.authenticate(UUID.randomUUID().toString(),
+ SecurityConfiguration.PASSWORD));
+ }
+ catch (Exception e) {
+ fail();
+ }
+ }
+
+ @Configuration
+ public static class SecurityConfiguration {
+
+ public static final String USERNAME = UUID.randomUUID().toString();
+
+ public static final String PASSWORD = UUID.randomUUID().toString();
+
+ @Bean
+ public AuthenticationManager authenticationManager() {
+ return new AuthenticationManager() {
+
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+ if (authentication.getName().equals(USERNAME) && authentication.getCredentials().equals(PASSWORD)) {
+ authentication = new UsernamePasswordAuthenticationToken(authentication.getPrincipal(),
+ authentication.getCredentials(), Collections.singleton(new SimpleGrantedAuthority("ROLE_ADMIN")));
+ }
+ else {
+ throw new BadCredentialsException("Invalid username and password");
+ }
+ return authentication;
+ }
+ };
+ }
+
+ @Bean
+ public AccessDecisionManager accessDecisionManager() {
+ List voters = new ArrayList();
+ voters.add(new RoleVoter());
+ AccessDecisionManager result = new UnanimousBased(voters);
+ return result;
+ }
+ }
+
+}
diff --git a/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/properties/CrshPropertiesTests.java b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/properties/CrshPropertiesTests.java
new file mode 100644
index 00000000000..d00cabcf2f1
--- /dev/null
+++ b/spring-boot-actuator/src/test/java/org/springframework/boot/actuate/properties/CrshPropertiesTests.java
@@ -0,0 +1,282 @@
+/*
+ * Copyright 2013 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.actuate.properties;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.boot.actuate.properties.CrshProperties.JaasAuthenticationProperties;
+import org.springframework.boot.actuate.properties.CrshProperties.KeyAuthenticationProperties;
+import org.springframework.boot.actuate.properties.CrshProperties.SimpleAuthenticationProperties;
+import org.springframework.boot.actuate.properties.CrshProperties.SpringAuthenticationProperties;
+import org.springframework.boot.bind.RelaxedDataBinder;
+import org.springframework.core.convert.support.DefaultConversionService;
+
+/**
+ * Tests for {@link CrshProperties}.
+ *
+ * @author Christian Dupuis
+ */
+public class CrshPropertiesTests {
+
+ @Test
+ public void testBindingAuth() {
+ CrshProperties props = new CrshProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
+ binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.auth", "spring")));
+ assertFalse(binder.getBindingResult().hasErrors());
+ assertEquals("spring", props.getAuth());
+ }
+
+ @Test
+ public void testBindingAuthIfEmpty() {
+ CrshProperties props = new CrshProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
+ binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.auth", "")));
+ assertTrue(binder.getBindingResult().hasErrors());
+ assertEquals("simple", props.getAuth());
+ }
+
+ @Test
+ public void testBindingCommandRefreshInterval() {
+ CrshProperties props = new CrshProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
+ binder.setConversionService(new DefaultConversionService());
+ binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.command_refresh_interval", "1")));
+ assertFalse(binder.getBindingResult().hasErrors());
+ assertEquals(1, props.getCommandRefreshInterval());
+ }
+
+ @Test
+ public void testBindingCommandPathPatterns() {
+ CrshProperties props = new CrshProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
+ binder.setConversionService(new DefaultConversionService());
+ binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.command_path_patterns",
+ "pattern1, pattern2")));
+ assertFalse(binder.getBindingResult().hasErrors());
+ assertEquals(2, props.getCommandPathPatterns().length);
+ Assert.assertArrayEquals(new String[] { "pattern1", "pattern2" }, props.getCommandPathPatterns());
+ }
+
+ @Test
+ public void testBindingConfigPathPatterns() {
+ CrshProperties props = new CrshProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
+ binder.setConversionService(new DefaultConversionService());
+ binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.config_path_patterns",
+ "pattern1, pattern2")));
+ assertFalse(binder.getBindingResult().hasErrors());
+ assertEquals(2, props.getConfigPathPatterns().length, 2);
+ Assert.assertArrayEquals(new String[] { "pattern1", "pattern2" }, props.getConfigPathPatterns());
+ }
+
+ @Test
+ public void testBindingDisabledPlugins() {
+ CrshProperties props = new CrshProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
+ binder.setConversionService(new DefaultConversionService());
+ binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.disabled_plugins",
+ "pattern1, pattern2")));
+ assertFalse(binder.getBindingResult().hasErrors());
+ assertEquals(2, props.getDisabledPlugins().length, 2);
+ assertArrayEquals(new String[] { "pattern1", "pattern2" }, props.getDisabledPlugins());
+ }
+
+ @Test
+ public void testBindingSsh() {
+ CrshProperties props = new CrshProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
+ binder.setConversionService(new DefaultConversionService());
+ Map map = new HashMap();
+ map.put("shell.ssh.enabled", "true");
+ map.put("shell.ssh.port", "2222");
+ map.put("shell.ssh.key_path", "~/.ssh/test.pem");
+ binder.bind(new MutablePropertyValues(map));
+ assertFalse(binder.getBindingResult().hasErrors());
+
+ Properties p = new Properties();
+ p = props.mergeProperties(p);
+
+ assertEquals("2222", p.get(CrshProperties.CRASH_SSH_PORT));
+ assertEquals("~/.ssh/test.pem", p.get(CrshProperties.CRASH_SSH_KEYPATH));
+ }
+
+ @Test
+ public void testBindingSshIgnored() {
+ CrshProperties props = new CrshProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
+ binder.setConversionService(new DefaultConversionService());
+ Map map = new HashMap();
+ map.put("shell.ssh.enabled", "false");
+ map.put("shell.ssh.port", "2222");
+ map.put("shell.ssh.key_path", "~/.ssh/test.pem");
+ binder.bind(new MutablePropertyValues(map));
+ assertFalse(binder.getBindingResult().hasErrors());
+
+ Properties p = new Properties();
+ p = props.mergeProperties(p);
+
+ assertNull(p.get(CrshProperties.CRASH_SSH_PORT));
+ assertNull(p.get(CrshProperties.CRASH_SSH_KEYPATH));
+ }
+
+ @Test
+ public void testBindingTelnet() {
+ CrshProperties props = new CrshProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
+ binder.setConversionService(new DefaultConversionService());
+ Map map = new HashMap();
+ map.put("shell.telnet.enabled", "true");
+ map.put("shell.telnet.port", "2222");
+ binder.bind(new MutablePropertyValues(map));
+ assertFalse(binder.getBindingResult().hasErrors());
+
+ Properties p = new Properties();
+ p = props.mergeProperties(p);
+
+ assertEquals("2222", p.get(CrshProperties.CRASH_TELNET_PORT));
+ }
+
+ @Test
+ public void testBindingTelnetIgnored() {
+ CrshProperties props = new CrshProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell");
+ binder.setConversionService(new DefaultConversionService());
+ Map map = new HashMap();
+ map.put("shell.telnet.enabled", "false");
+ map.put("shell.telnet.port", "2222");
+ binder.bind(new MutablePropertyValues(map));
+ assertFalse(binder.getBindingResult().hasErrors());
+
+ Properties p = new Properties();
+ p = props.mergeProperties(p);
+
+ assertNull(p.get(CrshProperties.CRASH_TELNET_PORT));
+ }
+
+ @Test
+ public void testBindingJaas() {
+ JaasAuthenticationProperties props = new JaasAuthenticationProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell.auth.jaas");
+ binder.setConversionService(new DefaultConversionService());
+ Map map = new HashMap();
+ map.put("shell.auth.jaas.domain", "my-test-domain");
+ binder.bind(new MutablePropertyValues(map));
+ assertFalse(binder.getBindingResult().hasErrors());
+
+ Properties p = new Properties();
+ p = props.mergeProperties(p);
+
+ assertEquals("my-test-domain", p.get(CrshProperties.CRASH_AUTH_JAAS_DOMAIN));
+ }
+
+ @Test
+ public void testBindingKey() {
+ KeyAuthenticationProperties props = new KeyAuthenticationProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell.auth.key");
+ binder.setConversionService(new DefaultConversionService());
+ Map map = new HashMap();
+ map.put("shell.auth.key.path", "~/.ssh/test.pem");
+ binder.bind(new MutablePropertyValues(map));
+ assertFalse(binder.getBindingResult().hasErrors());
+
+ Properties p = new Properties();
+ p = props.mergeProperties(p);
+
+ assertEquals("~/.ssh/test.pem", p.get(CrshProperties.CRASH_AUTH_KEY_PATH));
+ }
+
+ @Test
+ public void testBindingKeyIgnored() {
+ KeyAuthenticationProperties props = new KeyAuthenticationProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell.auth.key");
+ binder.setConversionService(new DefaultConversionService());
+ Map map = new HashMap();
+ binder.bind(new MutablePropertyValues(map));
+ assertFalse(binder.getBindingResult().hasErrors());
+
+ Properties p = new Properties();
+ p = props.mergeProperties(p);
+
+ assertNull(p.get(CrshProperties.CRASH_AUTH_KEY_PATH));
+ }
+
+ @Test
+ public void testBindingSimple() {
+ SimpleAuthenticationProperties props = new SimpleAuthenticationProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell.auth.simple");
+ binder.setConversionService(new DefaultConversionService());
+ Map map = new HashMap();
+ map.put("shell.auth.simple.username", "username123");
+ map.put("shell.auth.simple.password", "password123");
+ binder.bind(new MutablePropertyValues(map));
+ assertFalse(binder.getBindingResult().hasErrors());
+
+ Properties p = new Properties();
+ p = props.mergeProperties(p);
+
+ assertEquals("username123", p.get(CrshProperties.CRASH_AUTH_SIMPLE_USERNAME));
+ assertEquals("password123", p.get(CrshProperties.CRASH_AUTH_SIMPLE_PASSWORD));
+ }
+
+ @Test
+ public void testDefaultPasswordAutogeneratedIfUnresolovedPlaceholder() {
+ SimpleAuthenticationProperties security = new SimpleAuthenticationProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(security, "security");
+ binder.bind(new MutablePropertyValues(Collections.singletonMap(
+ "shell.auth.simple.password", "${ADMIN_PASSWORD}")));
+ assertFalse(binder.getBindingResult().hasErrors());
+ assertTrue(security.isDefaultPassword());
+ }
+
+ @Test
+ public void testDefaultPasswordAutogeneratedIfEmpty() {
+ SimpleAuthenticationProperties security = new SimpleAuthenticationProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(security, "security");
+ binder.bind(new MutablePropertyValues(Collections.singletonMap(
+ "shell.auth.simple.password", "")));
+ assertFalse(binder.getBindingResult().hasErrors());
+ assertTrue(security.isDefaultPassword());
+ }
+
+ @Test
+ public void testBindingSpring() {
+ SpringAuthenticationProperties props = new SpringAuthenticationProperties();
+ RelaxedDataBinder binder = new RelaxedDataBinder(props, "shell.auth.spring");
+ binder.bind(new MutablePropertyValues(Collections.singletonMap("shell.auth.spring.roles", "role1, role2")));
+ assertFalse(binder.getBindingResult().hasErrors());
+
+ Properties p = new Properties();
+ p = props.mergeProperties(p);
+
+ assertEquals("role1, role2", p.get(CrshProperties.CRASH_AUTH_SPRING_ROLES));
+ }
+
+
+}
diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml
index e14a5f02db2..14b3f94395a 100644
--- a/spring-boot-dependencies/pom.xml
+++ b/spring-boot-dependencies/pom.xml
@@ -47,6 +47,7 @@
2.0.1
1.1.3
7.0.42
+ 1.3.0-beta8
@@ -481,6 +482,31 @@
geronimo-jms_1.1_spec
1.1
+