|  | @@ -5,12 +5,27 @@
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  |  package org.elasticsearch.xpack.watcher.notification;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +import org.elasticsearch.common.settings.SecureSetting;
 | 
	
		
			
				|  |  | +import org.elasticsearch.common.settings.SecureSettings;
 | 
	
		
			
				|  |  | +import org.elasticsearch.common.settings.SecureString;
 | 
	
		
			
				|  |  | +import org.elasticsearch.common.settings.Setting;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.settings.Settings;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.settings.SettingsException;
 | 
	
		
			
				|  |  |  import org.elasticsearch.test.ESTestCase;
 | 
	
		
			
				|  |  |  import org.elasticsearch.xpack.watcher.notification.NotificationService;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +import java.io.IOException;
 | 
	
		
			
				|  |  | +import java.io.InputStream;
 | 
	
		
			
				|  |  | +import java.security.GeneralSecurityException;
 | 
	
		
			
				|  |  | +import java.util.Arrays;
 | 
	
		
			
				|  |  |  import java.util.Collections;
 | 
	
		
			
				|  |  | +import java.util.HashMap;
 | 
	
		
			
				|  |  | +import java.util.List;
 | 
	
		
			
				|  |  | +import java.util.Map;
 | 
	
		
			
				|  |  | +import java.util.Set;
 | 
	
		
			
				|  |  | +import java.util.concurrent.atomic.AtomicInteger;
 | 
	
		
			
				|  |  | +import java.util.concurrent.atomic.AtomicReference;
 | 
	
		
			
				|  |  | +import java.util.function.BiConsumer;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import static org.hamcrest.Matchers.anyOf;
 | 
	
		
			
				|  |  |  import static org.hamcrest.Matchers.is;
 | 
	
	
		
			
				|  | @@ -25,6 +40,7 @@ public class NotificationServiceTests extends ESTestCase {
 | 
	
		
			
				|  |  |          assertThat(service.getAccount(accountName), is(accountName));
 | 
	
		
			
				|  |  |          // single account, this will also be the default
 | 
	
		
			
				|  |  |          assertThat(service.getAccount("non-existing"), is(accountName));
 | 
	
		
			
				|  |  | +        assertThat(service.getAccount(null), is(accountName));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public void testMultipleAccountsWithExistingDefault() {
 | 
	
	
		
			
				|  | @@ -80,16 +96,160 @@ public class NotificationServiceTests extends ESTestCase {
 | 
	
		
			
				|  |  |                  is("no accounts of type [test] configured. Please set up an account using the [xpack.notification.test] settings"));
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    public void testAccountWithSecureSettings() throws Exception {
 | 
	
		
			
				|  |  | +        final Setting<SecureString> secureSetting1 = SecureSetting.secureString("xpack.notification.test.account.secure_only", null);
 | 
	
		
			
				|  |  | +        final Setting<SecureString> secureSetting2 = SecureSetting.secureString("xpack.notification.test.account.mixed.secure", null);
 | 
	
		
			
				|  |  | +        final Map<String, char[]> secureSettingsMap = new HashMap<>();
 | 
	
		
			
				|  |  | +        secureSettingsMap.put(secureSetting1.getKey(), "secure_only".toCharArray());
 | 
	
		
			
				|  |  | +        secureSettingsMap.put(secureSetting2.getKey(), "mixed_secure".toCharArray());
 | 
	
		
			
				|  |  | +        Settings settings = Settings.builder()
 | 
	
		
			
				|  |  | +                .put("xpack.notification.test.account.unsecure_only", "bar")
 | 
	
		
			
				|  |  | +                .put("xpack.notification.test.account.mixed.unsecure", "mixed_unsecure")
 | 
	
		
			
				|  |  | +                .setSecureSettings(secureSettingsFromMap(secureSettingsMap))
 | 
	
		
			
				|  |  | +                .build();
 | 
	
		
			
				|  |  | +        TestNotificationService service = new TestNotificationService(settings, Arrays.asList(secureSetting1, secureSetting2));
 | 
	
		
			
				|  |  | +        assertThat(service.getAccount("secure_only"), is("secure_only"));
 | 
	
		
			
				|  |  | +        assertThat(service.getAccount("unsecure_only"), is("unsecure_only"));
 | 
	
		
			
				|  |  | +        assertThat(service.getAccount("mixed"), is("mixed"));
 | 
	
		
			
				|  |  | +        assertThat(service.getAccount(null), anyOf(is("secure_only"), is("unsecure_only"), is("mixed")));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testAccountCreationCached() {
 | 
	
		
			
				|  |  | +        String accountName = randomAlphaOfLength(10);
 | 
	
		
			
				|  |  | +        Settings settings = Settings.builder().put("xpack.notification.test.account." + accountName, "bar").build();
 | 
	
		
			
				|  |  | +        final AtomicInteger validationInvocationCount = new AtomicInteger(0);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        TestNotificationService service = new TestNotificationService(settings, (String name, Settings accountSettings) -> {
 | 
	
		
			
				|  |  | +            validationInvocationCount.incrementAndGet();
 | 
	
		
			
				|  |  | +        });
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(0));
 | 
	
		
			
				|  |  | +        assertThat(service.getAccount(accountName), is(accountName));
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(1));
 | 
	
		
			
				|  |  | +        if (randomBoolean()) {
 | 
	
		
			
				|  |  | +            assertThat(service.getAccount(accountName), is(accountName));
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            assertThat(service.getAccount(null), is(accountName));
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        // counter is still 1 because the account is cached
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(1));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testAccountUpdateSettings() throws Exception {
 | 
	
		
			
				|  |  | +        final Setting<SecureString> secureSetting = SecureSetting.secureString("xpack.notification.test.account.x.secure", null);
 | 
	
		
			
				|  |  | +        final Setting<String> setting = Setting.simpleString("xpack.notification.test.account.x.dynamic", Setting.Property.Dynamic,
 | 
	
		
			
				|  |  | +                Setting.Property.NodeScope);
 | 
	
		
			
				|  |  | +        final AtomicReference<String> secureSettingValue = new AtomicReference<String>(randomAlphaOfLength(4));
 | 
	
		
			
				|  |  | +        final AtomicReference<String> settingValue = new AtomicReference<String>(randomAlphaOfLength(4));
 | 
	
		
			
				|  |  | +        final Map<String, char[]> secureSettingsMap = new HashMap<>();
 | 
	
		
			
				|  |  | +        final AtomicInteger validationInvocationCount = new AtomicInteger(0);
 | 
	
		
			
				|  |  | +        secureSettingsMap.put(secureSetting.getKey(), secureSettingValue.get().toCharArray());
 | 
	
		
			
				|  |  | +        final Settings.Builder settingsBuilder = Settings.builder()
 | 
	
		
			
				|  |  | +                .put(setting.getKey(), settingValue.get())
 | 
	
		
			
				|  |  | +                .setSecureSettings(secureSettingsFromMap(secureSettingsMap));
 | 
	
		
			
				|  |  | +        final TestNotificationService service = new TestNotificationService(settingsBuilder.build(), Arrays.asList(secureSetting),
 | 
	
		
			
				|  |  | +                (String name, Settings accountSettings) -> {
 | 
	
		
			
				|  |  | +                    assertThat(accountSettings.get("dynamic"), is(settingValue.get()));
 | 
	
		
			
				|  |  | +                    assertThat(SecureSetting.secureString("secure", null).get(accountSettings), is(secureSettingValue.get()));
 | 
	
		
			
				|  |  | +                    validationInvocationCount.incrementAndGet();
 | 
	
		
			
				|  |  | +                });
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(0));
 | 
	
		
			
				|  |  | +        service.getAccount(null);
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(1));
 | 
	
		
			
				|  |  | +        // update secure setting only
 | 
	
		
			
				|  |  | +        updateSecureSetting(secureSettingValue, secureSetting, secureSettingsMap, settingsBuilder, service);
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(1));
 | 
	
		
			
				|  |  | +        service.getAccount(null);
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(2));
 | 
	
		
			
				|  |  | +        updateDynamicClusterSetting(settingValue, setting, settingsBuilder, service);
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(2));
 | 
	
		
			
				|  |  | +        service.getAccount(null);
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(3));
 | 
	
		
			
				|  |  | +        // update both
 | 
	
		
			
				|  |  | +        if (randomBoolean()) {
 | 
	
		
			
				|  |  | +            // update secure first
 | 
	
		
			
				|  |  | +            updateSecureSetting(secureSettingValue, secureSetting, secureSettingsMap, settingsBuilder, service);
 | 
	
		
			
				|  |  | +            // update cluster second
 | 
	
		
			
				|  |  | +            updateDynamicClusterSetting(settingValue, setting, settingsBuilder, service);
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            // update cluster first
 | 
	
		
			
				|  |  | +            updateDynamicClusterSetting(settingValue, setting, settingsBuilder, service);
 | 
	
		
			
				|  |  | +            // update secure second
 | 
	
		
			
				|  |  | +            updateSecureSetting(secureSettingValue, secureSetting, secureSettingsMap, settingsBuilder, service);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(3));
 | 
	
		
			
				|  |  | +        service.getAccount(null);
 | 
	
		
			
				|  |  | +        assertThat(validationInvocationCount.get(), is(4));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private static void updateDynamicClusterSetting(AtomicReference<String> settingValue, Setting<String> setting,
 | 
	
		
			
				|  |  | +            Settings.Builder settingsBuilder, TestNotificationService service) {
 | 
	
		
			
				|  |  | +        settingValue.set(randomAlphaOfLength(4));
 | 
	
		
			
				|  |  | +        settingsBuilder.put(setting.getKey(), settingValue.get());
 | 
	
		
			
				|  |  | +        service.clusterSettingsConsumer(settingsBuilder.build());
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private static void updateSecureSetting(AtomicReference<String> secureSettingValue, Setting<SecureString> secureSetting,
 | 
	
		
			
				|  |  | +            Map<String, char[]> secureSettingsMap, Settings.Builder settingsBuilder, TestNotificationService service) {
 | 
	
		
			
				|  |  | +        secureSettingValue.set(randomAlphaOfLength(4));
 | 
	
		
			
				|  |  | +        secureSettingsMap.put(secureSetting.getKey(), secureSettingValue.get().toCharArray());
 | 
	
		
			
				|  |  | +        service.reload(settingsBuilder.build());
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      private static class TestNotificationService extends NotificationService<String> {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -        TestNotificationService(Settings settings) {
 | 
	
		
			
				|  |  | -            super("test", settings, Collections.emptyList());
 | 
	
		
			
				|  |  | +        private final BiConsumer<String, Settings> validator;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        TestNotificationService(Settings settings, List<Setting<?>> secureSettings, BiConsumer<String, Settings> validator) {
 | 
	
		
			
				|  |  | +            super("test", settings, secureSettings);
 | 
	
		
			
				|  |  | +            this.validator = validator;
 | 
	
		
			
				|  |  |              reload(settings);
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +        TestNotificationService(Settings settings, List<Setting<?>> secureSettings) {
 | 
	
		
			
				|  |  | +            this(settings, secureSettings, (x, y) -> {});
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        TestNotificationService(Settings settings) {
 | 
	
		
			
				|  |  | +            this(settings, Collections.emptyList(), (x, y) -> {});
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        TestNotificationService(Settings settings, BiConsumer<String, Settings> validator) {
 | 
	
		
			
				|  |  | +            this(settings, Collections.emptyList(), validator);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          @Override
 | 
	
		
			
				|  |  |          protected String createAccount(String name, Settings accountSettings) {
 | 
	
		
			
				|  |  | +            validator.accept(name, accountSettings);
 | 
	
		
			
				|  |  |              return name;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private static SecureSettings secureSettingsFromMap(Map<String, char[]> secureSettingsMap) {
 | 
	
		
			
				|  |  | +        return new SecureSettings() {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            @Override
 | 
	
		
			
				|  |  | +            public boolean isLoaded() {
 | 
	
		
			
				|  |  | +                return true;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            @Override
 | 
	
		
			
				|  |  | +            public SecureString getString(String setting) throws GeneralSecurityException {
 | 
	
		
			
				|  |  | +                return new SecureString(secureSettingsMap.get(setting));
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            @Override
 | 
	
		
			
				|  |  | +            public Set<String> getSettingNames() {
 | 
	
		
			
				|  |  | +                return secureSettingsMap.keySet();
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            @Override
 | 
	
		
			
				|  |  | +            public InputStream getFile(String setting) throws GeneralSecurityException {
 | 
	
		
			
				|  |  | +                return null;
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            @Override
 | 
	
		
			
				|  |  | +            public void close() throws IOException {
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +        };
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  |  }
 |