Merge branch 'master' into netty4_transport
* master: (25 commits) docs: remove unused file and add link to invalid role examples Remove interfaces for notification services Redirect to URL specified by next parameter, if it is set Fix fixture and tests so they pass again Update error message to be more actionable Switch to NamedWriteable pull based extension in xpack Fixing issue with infinite redirect loop Toggle display of security nav controls more dynamically Pass in xpackMainPlugin instead of xpackMainPlugin.info Wrap the return in a Promise Only unset the cookie if it is currently set Clarifying intent of code Updating tests fixtures + adding assertion for client cookie deletion If security is disabled, do not attempt to call the authenticate ES API Disambiguate between resolve function names Revert to not using xpackMainPlugin.info until the xpackMainPlugin is ready Redirect /login => / if security is disabled in ES Register/deregister security management items depending on whether there's an auth'd user Show/hide the username + logout button depending on whether there is an auth'd user If security is disabled, continue without auth + delete client cookie ... Original commit: elastic/x-pack-elasticsearch@16b92a1a59
This commit is contained in:
commit
4874d84f82
|
@ -5,16 +5,15 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.graph;
|
package org.elasticsearch.xpack.graph;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.license.XPackLicenseState;
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.xpack.XPackFeatureSet;
|
import org.elasticsearch.xpack.XPackFeatureSet;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -24,10 +23,9 @@ public class GraphFeatureSet implements XPackFeatureSet {
|
||||||
private final XPackLicenseState licenseState;
|
private final XPackLicenseState licenseState;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public GraphFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, NamedWriteableRegistry namedWriteableRegistry) {
|
public GraphFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState) {
|
||||||
this.enabled = Graph.enabled(settings);
|
this.enabled = Graph.enabled(settings);
|
||||||
this.licenseState = licenseState;
|
this.licenseState = licenseState;
|
||||||
namedWriteableRegistry.register(Usage.class, Usage.writeableName(Graph.NAME), Usage::new);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -55,7 +53,7 @@ public class GraphFeatureSet implements XPackFeatureSet {
|
||||||
return new Usage(available(), enabled());
|
return new Usage(available(), enabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Usage extends XPackFeatureSet.Usage {
|
public static class Usage extends XPackFeatureSet.Usage {
|
||||||
|
|
||||||
public Usage(StreamInput input) throws IOException {
|
public Usage(StreamInput input) throws IOException {
|
||||||
super(input);
|
super(input);
|
||||||
|
|
|
@ -5,17 +5,13 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.graph;
|
package org.elasticsearch.xpack.graph;
|
||||||
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.license.XPackLicenseState;
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import static org.hamcrest.core.Is.is;
|
import static org.hamcrest.core.Is.is;
|
||||||
import static org.mockito.Matchers.anyObject;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,21 +20,14 @@ import static org.mockito.Mockito.when;
|
||||||
public class GraphFeatureSetTests extends ESTestCase {
|
public class GraphFeatureSetTests extends ESTestCase {
|
||||||
|
|
||||||
private XPackLicenseState licenseState;
|
private XPackLicenseState licenseState;
|
||||||
private NamedWriteableRegistry namedWriteableRegistry;
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
licenseState = mock(XPackLicenseState.class);
|
licenseState = mock(XPackLicenseState.class);
|
||||||
namedWriteableRegistry = mock(NamedWriteableRegistry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testWritableRegistration() throws Exception {
|
|
||||||
new GraphFeatureSet(Settings.EMPTY, licenseState, namedWriteableRegistry);
|
|
||||||
verify(namedWriteableRegistry).register(eq(GraphFeatureSet.Usage.class), eq("xpack.usage.graph"), anyObject());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAvailable() throws Exception {
|
public void testAvailable() throws Exception {
|
||||||
GraphFeatureSet featureSet = new GraphFeatureSet(Settings.EMPTY, licenseState, namedWriteableRegistry);
|
GraphFeatureSet featureSet = new GraphFeatureSet(Settings.EMPTY, licenseState);
|
||||||
boolean available = randomBoolean();
|
boolean available = randomBoolean();
|
||||||
when(licenseState.isGraphAllowed()).thenReturn(available);
|
when(licenseState.isGraphAllowed()).thenReturn(available);
|
||||||
assertThat(featureSet.available(), is(available));
|
assertThat(featureSet.available(), is(available));
|
||||||
|
@ -54,7 +43,7 @@ public class GraphFeatureSetTests extends ESTestCase {
|
||||||
} else {
|
} else {
|
||||||
settings.put("xpack.graph.enabled", enabled);
|
settings.put("xpack.graph.enabled", enabled);
|
||||||
}
|
}
|
||||||
GraphFeatureSet featureSet = new GraphFeatureSet(settings.build(), licenseState, namedWriteableRegistry);
|
GraphFeatureSet featureSet = new GraphFeatureSet(settings.build(), licenseState);
|
||||||
assertThat(featureSet.enabled(), is(enabled));
|
assertThat(featureSet.enabled(), is(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,12 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.monitoring;
|
package org.elasticsearch.xpack.monitoring;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
@ -17,10 +20,6 @@ import org.elasticsearch.xpack.XPackFeatureSet;
|
||||||
import org.elasticsearch.xpack.monitoring.agent.exporter.Exporter;
|
import org.elasticsearch.xpack.monitoring.agent.exporter.Exporter;
|
||||||
import org.elasticsearch.xpack.monitoring.agent.exporter.Exporters;
|
import org.elasticsearch.xpack.monitoring.agent.exporter.Exporters;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -31,12 +30,10 @@ public class MonitoringFeatureSet implements XPackFeatureSet {
|
||||||
private final Exporters exporters;
|
private final Exporters exporters;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public MonitoringFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, @Nullable Exporters exporters,
|
public MonitoringFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, @Nullable Exporters exporters) {
|
||||||
NamedWriteableRegistry namedWriteableRegistry) {
|
|
||||||
this.enabled = MonitoringSettings.ENABLED.get(settings);
|
this.enabled = MonitoringSettings.ENABLED.get(settings);
|
||||||
this.licenseState = licenseState;
|
this.licenseState = licenseState;
|
||||||
this.exporters = exporters;
|
this.exporters = exporters;
|
||||||
namedWriteableRegistry.register(Usage.class, Usage.writeableName(Monitoring.NAME), Usage::new);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -79,7 +76,7 @@ public class MonitoringFeatureSet implements XPackFeatureSet {
|
||||||
return usage;
|
return usage;
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Usage extends XPackFeatureSet.Usage {
|
public static class Usage extends XPackFeatureSet.Usage {
|
||||||
|
|
||||||
private static final String ENABLED_EXPORTERS_XFIELD = "enabled_exporters";
|
private static final String ENABLED_EXPORTERS_XFIELD = "enabled_exporters";
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.monitoring;
|
package org.elasticsearch.xpack.monitoring;
|
||||||
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.license.XPackLicenseState;
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
@ -17,16 +19,10 @@ import org.elasticsearch.xpack.monitoring.agent.exporter.local.LocalExporter;
|
||||||
import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource;
|
import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
import static org.hamcrest.CoreMatchers.nullValue;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.hamcrest.core.Is.is;
|
import static org.hamcrest.core.Is.is;
|
||||||
import static org.mockito.Matchers.anyObject;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -35,23 +31,16 @@ import static org.mockito.Mockito.when;
|
||||||
public class MonitoringFeatureSetTests extends ESTestCase {
|
public class MonitoringFeatureSetTests extends ESTestCase {
|
||||||
|
|
||||||
private XPackLicenseState licenseState;
|
private XPackLicenseState licenseState;
|
||||||
private NamedWriteableRegistry namedWriteableRegistry;
|
|
||||||
private Exporters exporters;
|
private Exporters exporters;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
licenseState = mock(XPackLicenseState.class);
|
licenseState = mock(XPackLicenseState.class);
|
||||||
exporters = mock(Exporters.class);
|
exporters = mock(Exporters.class);
|
||||||
namedWriteableRegistry = mock(NamedWriteableRegistry.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testWritableRegistration() throws Exception {
|
|
||||||
new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters, namedWriteableRegistry);
|
|
||||||
verify(namedWriteableRegistry).register(eq(MonitoringFeatureSet.Usage.class), eq("xpack.usage.monitoring"), anyObject());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testAvailable() throws Exception {
|
public void testAvailable() throws Exception {
|
||||||
MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters, namedWriteableRegistry);
|
MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters);
|
||||||
boolean available = randomBoolean();
|
boolean available = randomBoolean();
|
||||||
when(licenseState.isMonitoringAllowed()).thenReturn(available);
|
when(licenseState.isMonitoringAllowed()).thenReturn(available);
|
||||||
assertThat(featureSet.available(), is(available));
|
assertThat(featureSet.available(), is(available));
|
||||||
|
@ -61,12 +50,12 @@ public class MonitoringFeatureSetTests extends ESTestCase {
|
||||||
boolean enabled = randomBoolean();
|
boolean enabled = randomBoolean();
|
||||||
Settings.Builder settings = Settings.builder();
|
Settings.Builder settings = Settings.builder();
|
||||||
settings.put("xpack.monitoring.enabled", enabled);
|
settings.put("xpack.monitoring.enabled", enabled);
|
||||||
MonitoringFeatureSet featureSet = new MonitoringFeatureSet(settings.build(), licenseState, exporters, namedWriteableRegistry);
|
MonitoringFeatureSet featureSet = new MonitoringFeatureSet(settings.build(), licenseState, exporters);
|
||||||
assertThat(featureSet.enabled(), is(enabled));
|
assertThat(featureSet.enabled(), is(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEnabledDefault() throws Exception {
|
public void testEnabledDefault() throws Exception {
|
||||||
MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters, namedWriteableRegistry);
|
MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters);
|
||||||
assertThat(featureSet.enabled(), is(true));
|
assertThat(featureSet.enabled(), is(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,7 +92,7 @@ public class MonitoringFeatureSetTests extends ESTestCase {
|
||||||
}
|
}
|
||||||
when(exporters.iterator()).thenReturn(exporterList.iterator());
|
when(exporters.iterator()).thenReturn(exporterList.iterator());
|
||||||
|
|
||||||
MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters, namedWriteableRegistry);
|
MonitoringFeatureSet featureSet = new MonitoringFeatureSet(Settings.EMPTY, licenseState, exporters);
|
||||||
XPackFeatureSet.Usage usage = featureSet.usage();
|
XPackFeatureSet.Usage usage = featureSet.usage();
|
||||||
assertThat(usage.name(), is(featureSet.name()));
|
assertThat(usage.name(), is(featureSet.name()));
|
||||||
assertThat(usage.enabled(), is(featureSet.enabled()));
|
assertThat(usage.enabled(), is(featureSet.enabled()));
|
||||||
|
|
|
@ -52,9 +52,8 @@ public class SecurityFeatureSet implements XPackFeatureSet {
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public SecurityFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, @Nullable Realms realms,
|
public SecurityFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, @Nullable Realms realms,
|
||||||
NamedWriteableRegistry namedWriteableRegistry, @Nullable CompositeRolesStore rolesStore,
|
@Nullable CompositeRolesStore rolesStore, @Nullable IPFilter ipFilter,
|
||||||
@Nullable IPFilter ipFilter, @Nullable AuditTrailService auditTrailService,
|
@Nullable AuditTrailService auditTrailService, @Nullable CryptoService cryptoService) {
|
||||||
@Nullable CryptoService cryptoService) {
|
|
||||||
this.enabled = Security.enabled(settings);
|
this.enabled = Security.enabled(settings);
|
||||||
this.licenseState = licenseState;
|
this.licenseState = licenseState;
|
||||||
this.realms = realms;
|
this.realms = realms;
|
||||||
|
@ -63,7 +62,6 @@ public class SecurityFeatureSet implements XPackFeatureSet {
|
||||||
this.ipFilter = ipFilter;
|
this.ipFilter = ipFilter;
|
||||||
this.auditTrailService = auditTrailService;
|
this.auditTrailService = auditTrailService;
|
||||||
this.cryptoService = cryptoService;
|
this.cryptoService = cryptoService;
|
||||||
namedWriteableRegistry.register(Usage.class, Usage.writeableName(Security.NAME), Usage::new);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -139,7 +137,7 @@ public class SecurityFeatureSet implements XPackFeatureSet {
|
||||||
return Collections.singletonMap("enabled", cryptoService != null && cryptoService.isEncryptionEnabled());
|
return Collections.singletonMap("enabled", cryptoService != null && cryptoService.isEncryptionEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Usage extends XPackFeatureSet.Usage {
|
public static class Usage extends XPackFeatureSet.Usage {
|
||||||
|
|
||||||
private static final String REALMS_XFIELD = "realms";
|
private static final String REALMS_XFIELD = "realms";
|
||||||
private static final String ROLES_XFIELD = "roles";
|
private static final String ROLES_XFIELD = "roles";
|
||||||
|
|
|
@ -5,8 +5,12 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.security;
|
package org.elasticsearch.xpack.security;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.elasticsearch.common.collect.MapBuilder;
|
import org.elasticsearch.common.collect.MapBuilder;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.license.XPackLicenseState;
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.test.ESTestCase;
|
import org.elasticsearch.test.ESTestCase;
|
||||||
|
@ -23,20 +27,12 @@ import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.nullValue;
|
import static org.hamcrest.CoreMatchers.nullValue;
|
||||||
import static org.hamcrest.Matchers.contains;
|
import static org.hamcrest.Matchers.contains;
|
||||||
import static org.hamcrest.Matchers.hasEntry;
|
import static org.hamcrest.Matchers.hasEntry;
|
||||||
import static org.hamcrest.Matchers.notNullValue;
|
import static org.hamcrest.Matchers.notNullValue;
|
||||||
import static org.hamcrest.core.Is.is;
|
import static org.hamcrest.core.Is.is;
|
||||||
import static org.mockito.Matchers.anyObject;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
public class SecurityFeatureSetTests extends ESTestCase {
|
public class SecurityFeatureSetTests extends ESTestCase {
|
||||||
|
@ -44,7 +40,6 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
||||||
private Settings settings;
|
private Settings settings;
|
||||||
private XPackLicenseState licenseState;
|
private XPackLicenseState licenseState;
|
||||||
private Realms realms;
|
private Realms realms;
|
||||||
private NamedWriteableRegistry namedWriteableRegistry;
|
|
||||||
private IPFilter ipFilter;
|
private IPFilter ipFilter;
|
||||||
private CompositeRolesStore rolesStore;
|
private CompositeRolesStore rolesStore;
|
||||||
private AuditTrailService auditTrail;
|
private AuditTrailService auditTrail;
|
||||||
|
@ -55,7 +50,6 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
||||||
settings = Settings.builder().put("path.home", createTempDir()).build();
|
settings = Settings.builder().put("path.home", createTempDir()).build();
|
||||||
licenseState = mock(XPackLicenseState.class);
|
licenseState = mock(XPackLicenseState.class);
|
||||||
realms = mock(Realms.class);
|
realms = mock(Realms.class);
|
||||||
namedWriteableRegistry = mock(NamedWriteableRegistry.class);
|
|
||||||
ipFilter = mock(IPFilter.class);
|
ipFilter = mock(IPFilter.class);
|
||||||
rolesStore = mock(CompositeRolesStore.class);
|
rolesStore = mock(CompositeRolesStore.class);
|
||||||
auditTrail = mock(AuditTrailService.class);
|
auditTrail = mock(AuditTrailService.class);
|
||||||
|
@ -67,13 +61,8 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
||||||
AnonymousUser.initialize(Settings.EMPTY);
|
AnonymousUser.initialize(Settings.EMPTY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWritableRegistration() throws Exception {
|
|
||||||
new SecurityFeatureSet(settings, licenseState, realms, namedWriteableRegistry, rolesStore, ipFilter, auditTrail, cryptoService);
|
|
||||||
verify(namedWriteableRegistry).register(eq(SecurityFeatureSet.Usage.class), eq("xpack.usage.security"), anyObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testAvailable() throws Exception {
|
public void testAvailable() throws Exception {
|
||||||
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings, licenseState, realms, namedWriteableRegistry, rolesStore,
|
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings, licenseState, realms, rolesStore,
|
||||||
ipFilter, auditTrail, cryptoService);
|
ipFilter, auditTrail, cryptoService);
|
||||||
boolean available = randomBoolean();
|
boolean available = randomBoolean();
|
||||||
when(licenseState.isAuthAllowed()).thenReturn(available);
|
when(licenseState.isAuthAllowed()).thenReturn(available);
|
||||||
|
@ -86,13 +75,13 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
||||||
.put(this.settings)
|
.put(this.settings)
|
||||||
.put("xpack.security.enabled", enabled)
|
.put("xpack.security.enabled", enabled)
|
||||||
.build();
|
.build();
|
||||||
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings, licenseState, realms, namedWriteableRegistry, rolesStore,
|
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings, licenseState, realms, rolesStore,
|
||||||
ipFilter, auditTrail, cryptoService);
|
ipFilter, auditTrail, cryptoService);
|
||||||
assertThat(featureSet.enabled(), is(enabled));
|
assertThat(featureSet.enabled(), is(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEnabledDefault() throws Exception {
|
public void testEnabledDefault() throws Exception {
|
||||||
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings, licenseState, realms, namedWriteableRegistry, rolesStore,
|
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings, licenseState, realms, rolesStore,
|
||||||
ipFilter, auditTrail, cryptoService);
|
ipFilter, auditTrail, cryptoService);
|
||||||
assertThat(featureSet.enabled(), is(true));
|
assertThat(featureSet.enabled(), is(true));
|
||||||
}
|
}
|
||||||
|
@ -164,7 +153,7 @@ public class SecurityFeatureSetTests extends ESTestCase {
|
||||||
AnonymousUser.initialize(Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "foo").build());
|
AnonymousUser.initialize(Settings.builder().put(AnonymousUser.ROLES_SETTING.getKey(), "foo").build());
|
||||||
}
|
}
|
||||||
|
|
||||||
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings.build(), licenseState, realms, namedWriteableRegistry, rolesStore,
|
SecurityFeatureSet featureSet = new SecurityFeatureSet(settings.build(), licenseState, realms, rolesStore,
|
||||||
ipFilter, auditTrail, cryptoService);
|
ipFilter, auditTrail, cryptoService);
|
||||||
XPackFeatureSet.Usage usage = featureSet.usage();
|
XPackFeatureSet.Usage usage = featureSet.usage();
|
||||||
assertThat(usage, is(notNullValue()));
|
assertThat(usage, is(notNullValue()));
|
||||||
|
|
|
@ -113,7 +113,7 @@ public class SecurityIndexSearcherWrapperUnitTests extends ESTestCase {
|
||||||
Collections.emptyMap(), Collections.emptyMap());
|
Collections.emptyMap(), Collections.emptyMap());
|
||||||
SimilarityService similarityService = new SimilarityService(indexSettings, Collections.emptyMap());
|
SimilarityService similarityService = new SimilarityService(indexSettings, Collections.emptyMap());
|
||||||
mapperService = new MapperService(indexSettings, analysisService, similarityService,
|
mapperService = new MapperService(indexSettings, analysisService, similarityService,
|
||||||
new IndicesModule(new NamedWriteableRegistry(), emptyList()).getMapperRegistry(), () -> null);
|
new IndicesModule(emptyList()).getMapperRegistry(), () -> null);
|
||||||
|
|
||||||
ShardId shardId = new ShardId(index, 0);
|
ShardId shardId = new ShardId(index, 0);
|
||||||
licenseState = mock(XPackLicenseState.class);
|
licenseState = mock(XPackLicenseState.class);
|
||||||
|
|
|
@ -61,7 +61,7 @@ public interface XPackFeatureSet {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getWriteableName() {
|
public String getWriteableName() {
|
||||||
return writeableName(name);
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -81,10 +81,6 @@ public interface XPackFeatureSet {
|
||||||
builder.field(AVAILABLE_XFIELD, available);
|
builder.field(AVAILABLE_XFIELD, available);
|
||||||
builder.field(ENABLED_XFIELD, enabled);
|
builder.field(ENABLED_XFIELD, enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String writeableName(String featureName) {
|
|
||||||
return "xpack.usage." + featureName;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import java.nio.file.Path;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -31,6 +32,7 @@ import org.elasticsearch.common.inject.Binder;
|
||||||
import org.elasticsearch.common.inject.Module;
|
import org.elasticsearch.common.inject.Module;
|
||||||
import org.elasticsearch.common.inject.multibindings.Multibinder;
|
import org.elasticsearch.common.inject.multibindings.Multibinder;
|
||||||
import org.elasticsearch.common.inject.util.Providers;
|
import org.elasticsearch.common.inject.util.Providers;
|
||||||
|
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||||
import org.elasticsearch.common.network.NetworkModule;
|
import org.elasticsearch.common.network.NetworkModule;
|
||||||
import org.elasticsearch.common.settings.Setting;
|
import org.elasticsearch.common.settings.Setting;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
@ -63,7 +65,9 @@ import org.elasticsearch.xpack.common.text.TextTemplateModule;
|
||||||
import org.elasticsearch.xpack.extensions.XPackExtension;
|
import org.elasticsearch.xpack.extensions.XPackExtension;
|
||||||
import org.elasticsearch.xpack.extensions.XPackExtensionsService;
|
import org.elasticsearch.xpack.extensions.XPackExtensionsService;
|
||||||
import org.elasticsearch.xpack.graph.Graph;
|
import org.elasticsearch.xpack.graph.Graph;
|
||||||
|
import org.elasticsearch.xpack.graph.GraphFeatureSet;
|
||||||
import org.elasticsearch.xpack.monitoring.Monitoring;
|
import org.elasticsearch.xpack.monitoring.Monitoring;
|
||||||
|
import org.elasticsearch.xpack.monitoring.MonitoringFeatureSet;
|
||||||
import org.elasticsearch.xpack.monitoring.MonitoringSettings;
|
import org.elasticsearch.xpack.monitoring.MonitoringSettings;
|
||||||
import org.elasticsearch.xpack.notification.Notification;
|
import org.elasticsearch.xpack.notification.Notification;
|
||||||
import org.elasticsearch.xpack.notification.email.Account;
|
import org.elasticsearch.xpack.notification.email.Account;
|
||||||
|
@ -72,11 +76,13 @@ import org.elasticsearch.xpack.rest.action.RestXPackInfoAction;
|
||||||
import org.elasticsearch.xpack.rest.action.RestXPackUsageAction;
|
import org.elasticsearch.xpack.rest.action.RestXPackUsageAction;
|
||||||
import org.elasticsearch.xpack.security.InternalClient;
|
import org.elasticsearch.xpack.security.InternalClient;
|
||||||
import org.elasticsearch.xpack.security.Security;
|
import org.elasticsearch.xpack.security.Security;
|
||||||
|
import org.elasticsearch.xpack.security.SecurityFeatureSet;
|
||||||
import org.elasticsearch.xpack.security.authc.AuthenticationService;
|
import org.elasticsearch.xpack.security.authc.AuthenticationService;
|
||||||
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
|
import org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken;
|
||||||
import org.elasticsearch.xpack.support.clock.Clock;
|
import org.elasticsearch.xpack.support.clock.Clock;
|
||||||
import org.elasticsearch.xpack.support.clock.SystemClock;
|
import org.elasticsearch.xpack.support.clock.SystemClock;
|
||||||
import org.elasticsearch.xpack.watcher.Watcher;
|
import org.elasticsearch.xpack.watcher.Watcher;
|
||||||
|
import org.elasticsearch.xpack.watcher.WatcherFeatureSet;
|
||||||
import org.elasticsearch.xpack.watcher.support.WatcherScript;
|
import org.elasticsearch.xpack.watcher.support.WatcherScript;
|
||||||
|
|
||||||
public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, IngestPlugin {
|
public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, IngestPlugin {
|
||||||
|
@ -180,13 +186,6 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
|
||||||
return modules;
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Class<? extends LifecycleComponent>> getGuiceServiceClasses() {
|
|
||||||
ArrayList<Class<? extends LifecycleComponent>> services = new ArrayList<>();
|
|
||||||
services.addAll(notification.nodeServices());
|
|
||||||
return services;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Collection<Object> createComponents(Client client, ClusterService clusterService, ThreadPool threadPool,
|
public Collection<Object> createComponents(Client client, ClusterService clusterService, ThreadPool threadPool,
|
||||||
ResourceWatcherService resourceWatcherService) {
|
ResourceWatcherService resourceWatcherService) {
|
||||||
|
@ -321,6 +320,16 @@ public class XPackPlugin extends Plugin implements ScriptPlugin, ActionPlugin, I
|
||||||
return security.getProcessors(parameters);
|
return security.getProcessors(parameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<NamedWriteableRegistry.Entry> getNamedWriteables() {
|
||||||
|
return Arrays.asList(
|
||||||
|
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, Security.NAME, SecurityFeatureSet.Usage::new),
|
||||||
|
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, Watcher.NAME, WatcherFeatureSet.Usage::new),
|
||||||
|
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, Monitoring.NAME, MonitoringFeatureSet.Usage::new),
|
||||||
|
new NamedWriteableRegistry.Entry(XPackFeatureSet.Usage.class, Graph.NAME, GraphFeatureSet.Usage::new)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public void onIndexModule(IndexModule module) {
|
public void onIndexModule(IndexModule module) {
|
||||||
security.onIndexModule(module);
|
security.onIndexModule(module);
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,29 +5,23 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.notification;
|
package org.elasticsearch.xpack.notification;
|
||||||
|
|
||||||
import org.elasticsearch.client.Client;
|
|
||||||
import org.elasticsearch.common.component.LifecycleComponent;
|
|
||||||
import org.elasticsearch.common.inject.Module;
|
|
||||||
import org.elasticsearch.common.settings.Setting;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.common.settings.SettingsModule;
|
|
||||||
import org.elasticsearch.xpack.common.http.HttpClient;
|
|
||||||
import org.elasticsearch.xpack.notification.email.EmailService;
|
|
||||||
import org.elasticsearch.xpack.notification.email.InternalEmailService;
|
|
||||||
import org.elasticsearch.xpack.notification.hipchat.HipChatService;
|
|
||||||
import org.elasticsearch.xpack.notification.hipchat.InternalHipChatService;
|
|
||||||
import org.elasticsearch.xpack.notification.pagerduty.InternalPagerDutyService;
|
|
||||||
import org.elasticsearch.xpack.notification.pagerduty.PagerDutyAccount;
|
|
||||||
import org.elasticsearch.xpack.notification.pagerduty.PagerDutyService;
|
|
||||||
import org.elasticsearch.xpack.notification.slack.InternalSlackService;
|
|
||||||
import org.elasticsearch.xpack.notification.slack.SlackService;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.elasticsearch.client.Client;
|
||||||
|
import org.elasticsearch.common.component.LifecycleComponent;
|
||||||
|
import org.elasticsearch.common.inject.Module;
|
||||||
|
import org.elasticsearch.common.settings.Setting;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.xpack.notification.email.EmailService;
|
||||||
|
import org.elasticsearch.xpack.notification.hipchat.HipChatService;
|
||||||
|
import org.elasticsearch.xpack.notification.pagerduty.PagerDutyAccount;
|
||||||
|
import org.elasticsearch.xpack.notification.pagerduty.PagerDutyService;
|
||||||
|
import org.elasticsearch.xpack.notification.slack.SlackService;
|
||||||
|
|
||||||
public class Notification {
|
public class Notification {
|
||||||
|
|
||||||
private final boolean transportClient;
|
private final boolean transportClient;
|
||||||
|
@ -37,10 +31,10 @@ public class Notification {
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Setting<?>> getSettings() {
|
public List<Setting<?>> getSettings() {
|
||||||
return Arrays.asList(InternalSlackService.SLACK_ACCOUNT_SETTING,
|
return Arrays.asList(SlackService.SLACK_ACCOUNT_SETTING,
|
||||||
InternalEmailService.EMAIL_ACCOUNT_SETTING,
|
EmailService.EMAIL_ACCOUNT_SETTING,
|
||||||
InternalHipChatService.HIPCHAT_ACCOUNT_SETTING,
|
HipChatService.HIPCHAT_ACCOUNT_SETTING,
|
||||||
InternalPagerDutyService.PAGERDUTY_ACCOUNT_SETTING);
|
PagerDutyService.PAGERDUTY_ACCOUNT_SETTING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getSettingsFilter() {
|
public List<String> getSettingsFilter() {
|
||||||
|
@ -54,18 +48,6 @@ public class Notification {
|
||||||
return settingsFilter;
|
return settingsFilter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<Class<? extends LifecycleComponent>> nodeServices() {
|
|
||||||
if (transportClient) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
return Arrays.<Class<? extends LifecycleComponent>>asList(
|
|
||||||
EmailService.class,
|
|
||||||
HipChatService.class,
|
|
||||||
SlackService.class,
|
|
||||||
PagerDutyService.class
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Collection<? extends Module> nodeModules() {
|
public Collection<? extends Module> nodeModules() {
|
||||||
if (transportClient) {
|
if (transportClient) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
|
|
@ -8,17 +8,12 @@ package org.elasticsearch.xpack.notification;
|
||||||
import org.elasticsearch.common.inject.AbstractModule;
|
import org.elasticsearch.common.inject.AbstractModule;
|
||||||
import org.elasticsearch.common.inject.multibindings.MapBinder;
|
import org.elasticsearch.common.inject.multibindings.MapBinder;
|
||||||
import org.elasticsearch.xpack.notification.email.EmailService;
|
import org.elasticsearch.xpack.notification.email.EmailService;
|
||||||
import org.elasticsearch.xpack.notification.email.HtmlSanitizer;
|
|
||||||
import org.elasticsearch.xpack.notification.email.InternalEmailService;
|
|
||||||
import org.elasticsearch.xpack.notification.email.attachment.DataAttachmentParser;
|
import org.elasticsearch.xpack.notification.email.attachment.DataAttachmentParser;
|
||||||
import org.elasticsearch.xpack.notification.email.attachment.EmailAttachmentParser;
|
import org.elasticsearch.xpack.notification.email.attachment.EmailAttachmentParser;
|
||||||
import org.elasticsearch.xpack.notification.email.attachment.EmailAttachmentsParser;
|
import org.elasticsearch.xpack.notification.email.attachment.EmailAttachmentsParser;
|
||||||
import org.elasticsearch.xpack.notification.email.attachment.HttpEmailAttachementParser;
|
import org.elasticsearch.xpack.notification.email.attachment.HttpEmailAttachementParser;
|
||||||
import org.elasticsearch.xpack.notification.hipchat.HipChatService;
|
import org.elasticsearch.xpack.notification.hipchat.HipChatService;
|
||||||
import org.elasticsearch.xpack.notification.hipchat.InternalHipChatService;
|
|
||||||
import org.elasticsearch.xpack.notification.pagerduty.InternalPagerDutyService;
|
|
||||||
import org.elasticsearch.xpack.notification.pagerduty.PagerDutyService;
|
import org.elasticsearch.xpack.notification.pagerduty.PagerDutyService;
|
||||||
import org.elasticsearch.xpack.notification.slack.InternalSlackService;
|
|
||||||
import org.elasticsearch.xpack.notification.slack.SlackService;
|
import org.elasticsearch.xpack.notification.slack.SlackService;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -40,9 +35,7 @@ public class NotificationModule extends AbstractModule {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
// email
|
bind(EmailService.class).asEagerSingleton();
|
||||||
bind(InternalEmailService.class).asEagerSingleton();
|
|
||||||
bind(EmailService.class).to(InternalEmailService.class).asEagerSingleton();
|
|
||||||
|
|
||||||
MapBinder<String, EmailAttachmentParser> emailParsersBinder = MapBinder.newMapBinder(binder(), String.class,
|
MapBinder<String, EmailAttachmentParser> emailParsersBinder = MapBinder.newMapBinder(binder(), String.class,
|
||||||
EmailAttachmentParser.class);
|
EmailAttachmentParser.class);
|
||||||
|
@ -51,16 +44,8 @@ public class NotificationModule extends AbstractModule {
|
||||||
}
|
}
|
||||||
bind(EmailAttachmentsParser.class).asEagerSingleton();
|
bind(EmailAttachmentsParser.class).asEagerSingleton();
|
||||||
|
|
||||||
// hipchat
|
bind(HipChatService.class).asEagerSingleton();
|
||||||
bind(InternalHipChatService.class).asEagerSingleton();
|
bind(SlackService.class).asEagerSingleton();
|
||||||
bind(HipChatService.class).to(InternalHipChatService.class);
|
bind(PagerDutyService.class).asEagerSingleton();
|
||||||
|
|
||||||
// slack
|
|
||||||
bind(InternalSlackService.class).asEagerSingleton();
|
|
||||||
bind(SlackService.class).to(InternalSlackService.class);
|
|
||||||
|
|
||||||
// pager duty
|
|
||||||
bind(InternalPagerDutyService.class).asEagerSingleton();
|
|
||||||
bind(PagerDutyService.class).to(InternalPagerDutyService.class);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,20 +5,69 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.notification.email;
|
package org.elasticsearch.xpack.notification.email;
|
||||||
|
|
||||||
import org.elasticsearch.common.component.LifecycleComponent;
|
|
||||||
|
|
||||||
import javax.mail.MessagingException;
|
import javax.mail.MessagingException;
|
||||||
|
|
||||||
|
import org.elasticsearch.common.Nullable;
|
||||||
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.logging.ESLogger;
|
||||||
|
import org.elasticsearch.common.settings.ClusterSettings;
|
||||||
|
import org.elasticsearch.common.settings.Setting;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.xpack.security.crypto.CryptoService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* A component to store email credentials and handle sending email notifications.
|
||||||
*/
|
*/
|
||||||
public interface EmailService extends LifecycleComponent{
|
public class EmailService extends AbstractComponent {
|
||||||
|
|
||||||
EmailSent send(Email email, Authentication auth, Profile profile) throws MessagingException;
|
private final CryptoService cryptoService;
|
||||||
|
public static final Setting<Settings> EMAIL_ACCOUNT_SETTING =
|
||||||
|
Setting.groupSetting("xpack.notification.email.", Setting.Property.Dynamic, Setting.Property.NodeScope);
|
||||||
|
|
||||||
EmailSent send(Email email, Authentication auth, Profile profile, String accountName) throws MessagingException;
|
private volatile Accounts accounts;
|
||||||
|
|
||||||
class EmailSent {
|
@Inject
|
||||||
|
public EmailService(Settings settings, @Nullable CryptoService cryptoService, ClusterSettings clusterSettings) {
|
||||||
|
super(settings);
|
||||||
|
this.cryptoService = cryptoService;
|
||||||
|
clusterSettings.addSettingsUpdateConsumer(EMAIL_ACCOUNT_SETTING, this::setEmailAccountSettings);
|
||||||
|
setEmailAccountSettings(EMAIL_ACCOUNT_SETTING.get(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setEmailAccountSettings(Settings settings) {
|
||||||
|
this.accounts = createAccounts(settings, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmailSent send(Email email, Authentication auth, Profile profile) throws MessagingException {
|
||||||
|
return send(email, auth, profile, (String) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EmailSent send(Email email, Authentication auth, Profile profile, String accountName) throws MessagingException {
|
||||||
|
Account account = accounts.account(accountName);
|
||||||
|
if (account == null) {
|
||||||
|
throw new IllegalArgumentException("failed to send email with subject [" + email.subject() + "] via account [" + accountName
|
||||||
|
+ "]. account does not exist");
|
||||||
|
}
|
||||||
|
return send(email, auth, profile, account);
|
||||||
|
}
|
||||||
|
|
||||||
|
EmailSent send(Email email, Authentication auth, Profile profile, Account account) throws MessagingException {
|
||||||
|
assert account != null;
|
||||||
|
try {
|
||||||
|
email = account.send(email, auth, profile);
|
||||||
|
} catch (MessagingException me) {
|
||||||
|
throw new MessagingException("failed to send email with subject [" + email.subject() + "] via account [" + account.name() +
|
||||||
|
"]", me);
|
||||||
|
}
|
||||||
|
return new EmailSent(account.name(), email);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Accounts createAccounts(Settings settings, ESLogger logger) {
|
||||||
|
return new Accounts(settings, cryptoService, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EmailSent {
|
||||||
|
|
||||||
private final String account;
|
private final String account;
|
||||||
private final Email email;
|
private final Email email;
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.notification.email;
|
|
||||||
|
|
||||||
import javax.mail.MessagingException;
|
|
||||||
|
|
||||||
import org.elasticsearch.ElasticsearchException;
|
|
||||||
import org.elasticsearch.common.Nullable;
|
|
||||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.logging.ESLogger;
|
|
||||||
import org.elasticsearch.common.settings.ClusterSettings;
|
|
||||||
import org.elasticsearch.common.settings.Setting;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.xpack.security.crypto.CryptoService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class InternalEmailService extends AbstractLifecycleComponent implements EmailService {
|
|
||||||
|
|
||||||
private final CryptoService cryptoService;
|
|
||||||
public static final Setting<Settings> EMAIL_ACCOUNT_SETTING =
|
|
||||||
Setting.groupSetting("xpack.notification.email.", Setting.Property.Dynamic, Setting.Property.NodeScope);
|
|
||||||
|
|
||||||
private volatile Accounts accounts;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public InternalEmailService(Settings settings, @Nullable CryptoService cryptoService, ClusterSettings clusterSettings) {
|
|
||||||
super(settings);
|
|
||||||
this.cryptoService = cryptoService;
|
|
||||||
clusterSettings.addSettingsUpdateConsumer(EMAIL_ACCOUNT_SETTING, this::setEmailAccountSettings);
|
|
||||||
setEmailAccountSettings(EMAIL_ACCOUNT_SETTING.get(settings));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setEmailAccountSettings(Settings settings) {
|
|
||||||
this.accounts = createAccounts(settings, logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() throws ElasticsearchException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStop() throws ElasticsearchException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doClose() throws ElasticsearchException {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EmailSent send(Email email, Authentication auth, Profile profile) throws MessagingException {
|
|
||||||
return send(email, auth, profile, (String) null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EmailSent send(Email email, Authentication auth, Profile profile, String accountName) throws MessagingException {
|
|
||||||
Account account = accounts.account(accountName);
|
|
||||||
if (account == null) {
|
|
||||||
throw new IllegalArgumentException("failed to send email with subject [" + email.subject() + "] via account [" + accountName
|
|
||||||
+ "]. account does not exist");
|
|
||||||
}
|
|
||||||
return send(email, auth, profile, account);
|
|
||||||
}
|
|
||||||
|
|
||||||
EmailSent send(Email email, Authentication auth, Profile profile, Account account) throws MessagingException {
|
|
||||||
assert account != null;
|
|
||||||
try {
|
|
||||||
email = account.send(email, auth, profile);
|
|
||||||
} catch (MessagingException me) {
|
|
||||||
throw new MessagingException("failed to send email with subject [" + email.subject() + "] via account [" + account.name() +
|
|
||||||
"]", me);
|
|
||||||
}
|
|
||||||
return new EmailSent(account.name(), email);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Accounts createAccounts(Settings settings, ESLogger logger) {
|
|
||||||
return new Accounts(settings, cryptoService, logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -5,22 +5,48 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.notification.hipchat;
|
package org.elasticsearch.xpack.notification.hipchat;
|
||||||
|
|
||||||
import org.elasticsearch.common.component.LifecycleComponent;
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.settings.ClusterSettings;
|
||||||
|
import org.elasticsearch.common.settings.Setting;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.xpack.common.http.HttpClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* A component to store hipchat credentials.
|
||||||
*/
|
*/
|
||||||
public interface HipChatService extends LifecycleComponent {
|
public class HipChatService extends AbstractComponent {
|
||||||
|
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
private volatile HipChatAccounts accounts;
|
||||||
|
public static final Setting<Settings> HIPCHAT_ACCOUNT_SETTING =
|
||||||
|
Setting.groupSetting("xpack.notification.hipchat.", Setting.Property.Dynamic, Setting.Property.NodeScope);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public HipChatService(Settings settings, HttpClient httpClient, ClusterSettings clusterSettings) {
|
||||||
|
super(settings);
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
clusterSettings.addSettingsUpdateConsumer(HIPCHAT_ACCOUNT_SETTING, this::setHipchatAccountSetting);
|
||||||
|
setHipchatAccountSetting(HIPCHAT_ACCOUNT_SETTING.get(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHipchatAccountSetting(Settings setting) {
|
||||||
|
accounts = new HipChatAccounts(setting, httpClient, logger);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The default hipchat account.
|
* @return The default hipchat account.
|
||||||
*/
|
*/
|
||||||
HipChatAccount getDefaultAccount();
|
public HipChatAccount getDefaultAccount() {
|
||||||
|
return accounts.account(null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The account identified by the given name. If the given name is {@code null} the default
|
* @return The account identified by the given name. If the given name is {@code null} the default
|
||||||
* account will be returned.
|
* account will be returned.
|
||||||
*/
|
*/
|
||||||
HipChatAccount getAccount(String accountName);
|
public HipChatAccount getAccount(String name) {
|
||||||
|
return accounts.account(name);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.notification.hipchat;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.ClusterSettings;
|
|
||||||
import org.elasticsearch.common.settings.Setting;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.xpack.common.http.HttpClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class InternalHipChatService extends AbstractLifecycleComponent implements HipChatService {
|
|
||||||
|
|
||||||
private final HttpClient httpClient;
|
|
||||||
private volatile HipChatAccounts accounts;
|
|
||||||
public static final Setting<Settings> HIPCHAT_ACCOUNT_SETTING =
|
|
||||||
Setting.groupSetting("xpack.notification.hipchat.", Setting.Property.Dynamic, Setting.Property.NodeScope);
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public InternalHipChatService(Settings settings, HttpClient httpClient, ClusterSettings clusterSettings) {
|
|
||||||
super(settings);
|
|
||||||
this.httpClient = httpClient;
|
|
||||||
clusterSettings.addSettingsUpdateConsumer(HIPCHAT_ACCOUNT_SETTING, this::setHipchatAccountSetting);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() {
|
|
||||||
setHipchatAccountSetting(HIPCHAT_ACCOUNT_SETTING.get(settings));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStop() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doClose() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setHipchatAccountSetting(Settings setting) {
|
|
||||||
accounts = new HipChatAccounts(setting, httpClient, logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HipChatAccount getDefaultAccount() {
|
|
||||||
return accounts.account(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public HipChatAccount getAccount(String name) {
|
|
||||||
return accounts.account(name);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,59 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.notification.pagerduty;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.ClusterSettings;
|
|
||||||
import org.elasticsearch.common.settings.Setting;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.xpack.common.http.HttpClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class InternalPagerDutyService extends AbstractLifecycleComponent implements PagerDutyService {
|
|
||||||
|
|
||||||
public static final Setting<Settings> PAGERDUTY_ACCOUNT_SETTING =
|
|
||||||
Setting.groupSetting("xpack.notification.pagerduty.", Setting.Property.Dynamic, Setting.Property.NodeScope);
|
|
||||||
|
|
||||||
private final HttpClient httpClient;
|
|
||||||
private volatile PagerDutyAccounts accounts;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public InternalPagerDutyService(Settings settings, HttpClient httpClient, ClusterSettings clusterSettings) {
|
|
||||||
super(settings);
|
|
||||||
this.httpClient = httpClient;
|
|
||||||
clusterSettings.addSettingsUpdateConsumer(PAGERDUTY_ACCOUNT_SETTING, this::setPagerDutyAccountSetting);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() {
|
|
||||||
setPagerDutyAccountSetting(PAGERDUTY_ACCOUNT_SETTING.get(settings));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStop() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doClose() {
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setPagerDutyAccountSetting(Settings settings) {
|
|
||||||
accounts = new PagerDutyAccounts(settings, httpClient, logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PagerDutyAccount getDefaultAccount() {
|
|
||||||
return accounts.account(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PagerDutyAccount getAccount(String name) {
|
|
||||||
return accounts.account(name);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,14 +5,41 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.notification.pagerduty;
|
package org.elasticsearch.xpack.notification.pagerduty;
|
||||||
|
|
||||||
import org.elasticsearch.common.component.LifecycleComponent;
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.settings.ClusterSettings;
|
||||||
|
import org.elasticsearch.common.settings.Setting;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.xpack.common.http.HttpClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* A component to store pagerduty credentials.
|
||||||
*/
|
*/
|
||||||
public interface PagerDutyService extends LifecycleComponent {
|
public class PagerDutyService extends AbstractComponent {
|
||||||
|
|
||||||
PagerDutyAccount getDefaultAccount();
|
public static final Setting<Settings> PAGERDUTY_ACCOUNT_SETTING =
|
||||||
|
Setting.groupSetting("xpack.notification.pagerduty.", Setting.Property.Dynamic, Setting.Property.NodeScope);
|
||||||
|
|
||||||
PagerDutyAccount getAccount(String accountName);
|
private final HttpClient httpClient;
|
||||||
|
private volatile PagerDutyAccounts accounts;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PagerDutyService(Settings settings, HttpClient httpClient, ClusterSettings clusterSettings) {
|
||||||
|
super(settings);
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
clusterSettings.addSettingsUpdateConsumer(PAGERDUTY_ACCOUNT_SETTING, this::setPagerDutyAccountSetting);
|
||||||
|
setPagerDutyAccountSetting(PAGERDUTY_ACCOUNT_SETTING.get(settings));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setPagerDutyAccountSetting(Settings settings) {
|
||||||
|
accounts = new PagerDutyAccounts(settings, httpClient, logger);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PagerDutyAccount getDefaultAccount() {
|
||||||
|
return accounts.account(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PagerDutyAccount getAccount(String name) {
|
||||||
|
return accounts.account(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
|
||||||
* or more contributor license agreements. Licensed under the Elastic License;
|
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
|
||||||
*/
|
|
||||||
package org.elasticsearch.xpack.notification.slack;
|
|
||||||
|
|
||||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
|
||||||
import org.elasticsearch.common.inject.Inject;
|
|
||||||
import org.elasticsearch.common.settings.ClusterSettings;
|
|
||||||
import org.elasticsearch.common.settings.Setting;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
|
||||||
import org.elasticsearch.xpack.common.http.HttpClient;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public class InternalSlackService extends AbstractLifecycleComponent implements SlackService {
|
|
||||||
|
|
||||||
private final HttpClient httpClient;
|
|
||||||
public static final Setting<Settings> SLACK_ACCOUNT_SETTING =
|
|
||||||
Setting.groupSetting("xpack.notification.slack.", Setting.Property.Dynamic, Setting.Property.NodeScope);
|
|
||||||
private volatile SlackAccounts accounts;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public InternalSlackService(Settings settings, HttpClient httpClient, ClusterSettings clusterSettings) {
|
|
||||||
super(settings);
|
|
||||||
this.httpClient = httpClient;
|
|
||||||
clusterSettings.addSettingsUpdateConsumer(SLACK_ACCOUNT_SETTING, this::setSlackAccountSetting);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() {
|
|
||||||
setSlackAccountSetting(SLACK_ACCOUNT_SETTING.get(settings));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStop() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doClose() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SlackAccount getDefaultAccount() {
|
|
||||||
return accounts.account(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSlackAccountSetting(Settings setting) {
|
|
||||||
accounts = new SlackAccounts(setting, httpClient, logger);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public SlackAccount getAccount(String name) {
|
|
||||||
return accounts.account(name);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,22 +5,47 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.notification.slack;
|
package org.elasticsearch.xpack.notification.slack;
|
||||||
|
|
||||||
import org.elasticsearch.common.component.LifecycleComponent;
|
import org.elasticsearch.common.component.AbstractComponent;
|
||||||
|
import org.elasticsearch.common.inject.Inject;
|
||||||
|
import org.elasticsearch.common.settings.ClusterSettings;
|
||||||
|
import org.elasticsearch.common.settings.Setting;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.xpack.common.http.HttpClient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
* A component to store slack credentials.
|
||||||
*/
|
*/
|
||||||
public interface SlackService extends LifecycleComponent {
|
public class SlackService extends AbstractComponent {
|
||||||
|
|
||||||
|
private final HttpClient httpClient;
|
||||||
|
public static final Setting<Settings> SLACK_ACCOUNT_SETTING =
|
||||||
|
Setting.groupSetting("xpack.notification.slack.", Setting.Property.Dynamic, Setting.Property.NodeScope);
|
||||||
|
private volatile SlackAccounts accounts;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SlackService(Settings settings, HttpClient httpClient, ClusterSettings clusterSettings) {
|
||||||
|
super(settings);
|
||||||
|
this.httpClient = httpClient;
|
||||||
|
clusterSettings.addSettingsUpdateConsumer(SLACK_ACCOUNT_SETTING, this::setSlackAccountSetting);
|
||||||
|
setSlackAccountSetting(SLACK_ACCOUNT_SETTING.get(settings));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The default slack account.
|
* @return The default slack account.
|
||||||
*/
|
*/
|
||||||
SlackAccount getDefaultAccount();
|
public SlackAccount getDefaultAccount() {
|
||||||
|
return accounts.account(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setSlackAccountSetting(Settings setting) {
|
||||||
|
accounts = new SlackAccounts(setting, httpClient, logger);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return The account identified by the given name. If the given name is {@code null} the default
|
* @return The account identified by the given name. If the given name is {@code null} the default
|
||||||
* account will be returned.
|
* account will be returned.
|
||||||
*/
|
*/
|
||||||
SlackAccount getAccount(String accountName);
|
public SlackAccount getAccount(String name) {
|
||||||
|
return accounts.account(name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,29 +22,20 @@ import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
public class EmailServiceTests extends ESTestCase {
|
||||||
*
|
private EmailService service;
|
||||||
*/
|
|
||||||
public class InternalEmailServiceTests extends ESTestCase {
|
|
||||||
private InternalEmailService service;
|
|
||||||
private Accounts accounts;
|
private Accounts accounts;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
accounts = mock(Accounts.class);
|
accounts = mock(Accounts.class);
|
||||||
service = new InternalEmailService(Settings.EMPTY, null,
|
service = new EmailService(Settings.EMPTY, null,
|
||||||
new ClusterSettings(Settings.EMPTY, Collections.singleton(InternalEmailService.EMAIL_ACCOUNT_SETTING))) {
|
new ClusterSettings(Settings.EMPTY, Collections.singleton(EmailService.EMAIL_ACCOUNT_SETTING))) {
|
||||||
@Override
|
@Override
|
||||||
protected Accounts createAccounts(Settings settings, ESLogger logger) {
|
protected Accounts createAccounts(Settings settings, ESLogger logger) {
|
||||||
return accounts;
|
return accounts;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
service.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void cleanup() throws Exception {
|
|
||||||
service.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSend() throws Exception {
|
public void testSend() throws Exception {
|
|
@ -89,42 +89,35 @@ public class ManualPublicSmtpServersTester {
|
||||||
|
|
||||||
static void test(Profile profile, Settings.Builder settingsBuilder) throws Exception {
|
static void test(Profile profile, Settings.Builder settingsBuilder) throws Exception {
|
||||||
String path = "/org/elasticsearch/xpack/watcher/actions/email/service/logo.png";
|
String path = "/org/elasticsearch/xpack/watcher/actions/email/service/logo.png";
|
||||||
if (InternalEmailServiceTests.class.getResourceAsStream(path) == null) {
|
if (EmailServiceTests.class.getResourceAsStream(path) == null) {
|
||||||
throw new ElasticsearchException("Could not find logo at path {}", path);
|
throw new ElasticsearchException("Could not find logo at path {}", path);
|
||||||
}
|
}
|
||||||
|
|
||||||
InternalEmailService service = startEmailService(settingsBuilder);
|
EmailService service = startEmailService(settingsBuilder);
|
||||||
try {
|
ToXContent content = (xContentBuilder, params) -> xContentBuilder.startObject()
|
||||||
|
.field("key1", "value1")
|
||||||
|
.field("key2", "value2")
|
||||||
|
.field("key3", "value3")
|
||||||
|
.endObject();
|
||||||
|
|
||||||
ToXContent content = (xContentBuilder, params) -> xContentBuilder.startObject()
|
Email email = Email.builder()
|
||||||
.field("key1", "value1")
|
.id("_id")
|
||||||
.field("key2", "value2")
|
.subject("_subject")
|
||||||
.field("key3", "value3")
|
.textBody("_text_body")
|
||||||
.endObject();
|
.htmlBody("<b>html body</b><p/><p/><img src=\"cid:logo.png\"/>")
|
||||||
|
.attach(new Attachment.XContent.Yaml("test.yml", content))
|
||||||
|
.attach(new Attachment.Stream("logo.png", "logo.png", true,
|
||||||
|
() -> EmailServiceTests.class.getResourceAsStream(path)))
|
||||||
|
.build();
|
||||||
|
|
||||||
Email email = Email.builder()
|
EmailService.EmailSent sent = service.send(email, null, profile);
|
||||||
.id("_id")
|
|
||||||
.subject("_subject")
|
|
||||||
.textBody("_text_body")
|
|
||||||
.htmlBody("<b>html body</b><p/><p/><img src=\"cid:logo.png\"/>")
|
|
||||||
.attach(new Attachment.XContent.Yaml("test.yml", content))
|
|
||||||
.attach(new Attachment.Stream("logo.png", "logo.png", true,
|
|
||||||
() -> InternalEmailServiceTests.class.getResourceAsStream(path)))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
EmailService.EmailSent sent = service.send(email, null, profile);
|
terminal.println(String.format(Locale.ROOT, "email sent via account [%s]", sent.account()));
|
||||||
|
|
||||||
terminal.println(String.format(Locale.ROOT, "email sent via account [%s]", sent.account()));
|
|
||||||
} finally {
|
|
||||||
service.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static InternalEmailService startEmailService(Settings.Builder builder) {
|
static EmailService startEmailService(Settings.Builder builder) {
|
||||||
Settings settings = builder.build();
|
Settings settings = builder.build();
|
||||||
InternalEmailService service = new InternalEmailService(settings, null,
|
return new EmailService(settings, null,
|
||||||
new ClusterSettings(settings, Collections.singleton(InternalEmailService.EMAIL_ACCOUNT_SETTING)));
|
new ClusterSettings(settings, Collections.singleton(EmailService.EMAIL_ACCOUNT_SETTING)));
|
||||||
service.start();
|
|
||||||
return service;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ public class ProfileTests extends ESTestCase {
|
||||||
public void testThatInlineAttachmentsAreCreated() throws Exception {
|
public void testThatInlineAttachmentsAreCreated() throws Exception {
|
||||||
String path = "/org/elasticsearch/xpack/watcher/actions/email/service/logo.png";
|
String path = "/org/elasticsearch/xpack/watcher/actions/email/service/logo.png";
|
||||||
Attachment attachment = new Attachment.Stream("inline.png", "inline.png", true,
|
Attachment attachment = new Attachment.Stream("inline.png", "inline.png", true,
|
||||||
() -> InternalEmailServiceTests.class.getResourceAsStream(path));
|
() -> EmailServiceTests.class.getResourceAsStream(path));
|
||||||
|
|
||||||
Email email = Email.builder()
|
Email email = Email.builder()
|
||||||
.id("foo")
|
.id("foo")
|
||||||
|
|
|
@ -26,7 +26,7 @@ import static org.mockito.Mockito.mock;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public class InternalHipChatServiceTests extends ESTestCase {
|
public class HipChatServiceTests extends ESTestCase {
|
||||||
private HttpClient httpClient;
|
private HttpClient httpClient;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@ -53,9 +53,8 @@ public class InternalHipChatServiceTests extends ESTestCase {
|
||||||
settingsBuilder.put("xpack.notification.hipchat.account." + accountName + ".port", port);
|
settingsBuilder.put("xpack.notification.hipchat.account." + accountName + ".port", port);
|
||||||
}
|
}
|
||||||
buildMessageDefaults(accountName, settingsBuilder, defaultRoom, null, defaultFrom, defaultColor, defaultFormat, defaultNotify);
|
buildMessageDefaults(accountName, settingsBuilder, defaultRoom, null, defaultFrom, defaultColor, defaultFormat, defaultNotify);
|
||||||
InternalHipChatService service = new InternalHipChatService(settingsBuilder.build(), httpClient,
|
HipChatService service = new HipChatService(settingsBuilder.build(), httpClient,
|
||||||
new ClusterSettings(settingsBuilder.build(), Collections.singleton(InternalHipChatService.HIPCHAT_ACCOUNT_SETTING)));
|
new ClusterSettings(settingsBuilder.build(), Collections.singleton(HipChatService.HIPCHAT_ACCOUNT_SETTING)));
|
||||||
service.start();
|
|
||||||
|
|
||||||
HipChatAccount account = service.getAccount(accountName);
|
HipChatAccount account = service.getAccount(accountName);
|
||||||
assertThat(account, notNullValue());
|
assertThat(account, notNullValue());
|
||||||
|
@ -102,9 +101,8 @@ public class InternalHipChatServiceTests extends ESTestCase {
|
||||||
settingsBuilder.put("xpack.notification.hipchat.account." + accountName + ".port", port);
|
settingsBuilder.put("xpack.notification.hipchat.account." + accountName + ".port", port);
|
||||||
}
|
}
|
||||||
buildMessageDefaults(accountName, settingsBuilder, null, null, defaultFrom, defaultColor, defaultFormat, defaultNotify);
|
buildMessageDefaults(accountName, settingsBuilder, null, null, defaultFrom, defaultColor, defaultFormat, defaultNotify);
|
||||||
InternalHipChatService service = new InternalHipChatService(settingsBuilder.build(), httpClient,
|
HipChatService service = new HipChatService(settingsBuilder.build(), httpClient,
|
||||||
new ClusterSettings(settingsBuilder.build(), Collections.singleton(InternalHipChatService.HIPCHAT_ACCOUNT_SETTING)));
|
new ClusterSettings(settingsBuilder.build(), Collections.singleton(HipChatService.HIPCHAT_ACCOUNT_SETTING)));
|
||||||
service.start();
|
|
||||||
|
|
||||||
HipChatAccount account = service.getAccount(accountName);
|
HipChatAccount account = service.getAccount(accountName);
|
||||||
assertThat(account, notNullValue());
|
assertThat(account, notNullValue());
|
||||||
|
@ -131,13 +129,10 @@ public class InternalHipChatServiceTests extends ESTestCase {
|
||||||
.put("xpack.notification.hipchat.account." + accountName + ".profile",
|
.put("xpack.notification.hipchat.account." + accountName + ".profile",
|
||||||
HipChatAccount.Profile.INTEGRATION.value())
|
HipChatAccount.Profile.INTEGRATION.value())
|
||||||
.put("xpack.notification.hipchat.account." + accountName + ".auth_token", "_token");
|
.put("xpack.notification.hipchat.account." + accountName + ".auth_token", "_token");
|
||||||
try (InternalHipChatService service = new InternalHipChatService(settingsBuilder.build(), httpClient,
|
SettingsException e = expectThrows(SettingsException.class, () ->
|
||||||
new ClusterSettings(settingsBuilder.build(), Collections.singleton(InternalHipChatService.HIPCHAT_ACCOUNT_SETTING)))) {
|
new HipChatService(settingsBuilder.build(), httpClient,
|
||||||
service.start();
|
new ClusterSettings(settingsBuilder.build(), Collections.singleton(HipChatService.HIPCHAT_ACCOUNT_SETTING))));
|
||||||
fail("Expected SettingsException");
|
assertThat(e.getMessage(), containsString("missing required [room] setting for [integration] account profile"));
|
||||||
} catch (SettingsException e) {
|
|
||||||
assertThat(e.getMessage(), containsString("missing required [room] setting for [integration] account profile"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSingleAccountUser() throws Exception {
|
public void testSingleAccountUser() throws Exception {
|
||||||
|
@ -159,9 +154,8 @@ public class InternalHipChatServiceTests extends ESTestCase {
|
||||||
settingsBuilder.put("xpack.notification.hipchat.account." + accountName + ".port", port);
|
settingsBuilder.put("xpack.notification.hipchat.account." + accountName + ".port", port);
|
||||||
}
|
}
|
||||||
buildMessageDefaults(accountName, settingsBuilder, defaultRoom, defaultUser, null, defaultColor, defaultFormat, defaultNotify);
|
buildMessageDefaults(accountName, settingsBuilder, defaultRoom, defaultUser, null, defaultColor, defaultFormat, defaultNotify);
|
||||||
InternalHipChatService service = new InternalHipChatService(settingsBuilder.build(), httpClient,
|
HipChatService service = new HipChatService(settingsBuilder.build(), httpClient,
|
||||||
new ClusterSettings(settingsBuilder.build(), Collections.singleton(InternalHipChatService.HIPCHAT_ACCOUNT_SETTING)));
|
new ClusterSettings(settingsBuilder.build(), Collections.singleton(HipChatService.HIPCHAT_ACCOUNT_SETTING)));
|
||||||
service.start();
|
|
||||||
|
|
||||||
HipChatAccount account = service.getAccount(accountName);
|
HipChatAccount account = service.getAccount(accountName);
|
||||||
assertThat(account, notNullValue());
|
assertThat(account, notNullValue());
|
||||||
|
@ -221,9 +215,8 @@ public class InternalHipChatServiceTests extends ESTestCase {
|
||||||
buildMessageDefaults(name, settingsBuilder, null, null, null, defaultColor, defaultFormat, defaultNotify);
|
buildMessageDefaults(name, settingsBuilder, null, null, null, defaultColor, defaultFormat, defaultNotify);
|
||||||
}
|
}
|
||||||
|
|
||||||
InternalHipChatService service = new InternalHipChatService(settingsBuilder.build(), httpClient,
|
HipChatService service = new HipChatService(settingsBuilder.build(), httpClient,
|
||||||
new ClusterSettings(settingsBuilder.build(), Collections.singleton(InternalHipChatService.HIPCHAT_ACCOUNT_SETTING)));
|
new ClusterSettings(settingsBuilder.build(), Collections.singleton(HipChatService.HIPCHAT_ACCOUNT_SETTING)));
|
||||||
service.start();
|
|
||||||
|
|
||||||
for (int i = 0; i < 5; i++) {
|
for (int i = 0; i < 5; i++) {
|
||||||
String name = "_a" + i;
|
String name = "_a" + i;
|
|
@ -5,9 +5,12 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.watcher;
|
package org.elasticsearch.xpack.watcher;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.elasticsearch.common.Nullable;
|
import org.elasticsearch.common.Nullable;
|
||||||
import org.elasticsearch.common.inject.Inject;
|
import org.elasticsearch.common.inject.Inject;
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
|
||||||
import org.elasticsearch.common.io.stream.StreamInput;
|
import org.elasticsearch.common.io.stream.StreamInput;
|
||||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
@ -15,10 +18,6 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.license.XPackLicenseState;
|
import org.elasticsearch.license.XPackLicenseState;
|
||||||
import org.elasticsearch.xpack.XPackFeatureSet;
|
import org.elasticsearch.xpack.XPackFeatureSet;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
@ -29,12 +28,10 @@ public class WatcherFeatureSet implements XPackFeatureSet {
|
||||||
private final WatcherService watcherService;
|
private final WatcherService watcherService;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public WatcherFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, NamedWriteableRegistry namedWriteableRegistry,
|
public WatcherFeatureSet(Settings settings, @Nullable XPackLicenseState licenseState, @Nullable WatcherService watcherService) {
|
||||||
@Nullable WatcherService watcherService) {
|
|
||||||
this.watcherService = watcherService;
|
this.watcherService = watcherService;
|
||||||
this.enabled = Watcher.enabled(settings);
|
this.enabled = Watcher.enabled(settings);
|
||||||
this.licenseState = licenseState;
|
this.licenseState = licenseState;
|
||||||
namedWriteableRegistry.register(Usage.class, Usage.writeableName(Watcher.NAME), Usage::new);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -62,7 +59,7 @@ public class WatcherFeatureSet implements XPackFeatureSet {
|
||||||
return new Usage(available(), enabled(), watcherService != null ? watcherService.usageStats() : Collections.emptyMap());
|
return new Usage(available(), enabled(), watcherService != null ? watcherService.usageStats() : Collections.emptyMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
static class Usage extends XPackFeatureSet.Usage {
|
public static class Usage extends XPackFeatureSet.Usage {
|
||||||
|
|
||||||
private final Map<String, Object> stats;
|
private final Map<String, Object> stats;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
*/
|
*/
|
||||||
package org.elasticsearch.xpack.watcher;
|
package org.elasticsearch.xpack.watcher;
|
||||||
|
|
||||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.xcontent.ToXContent;
|
import org.elasticsearch.common.xcontent.ToXContent;
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
|
@ -14,17 +16,11 @@ import org.elasticsearch.test.ESTestCase;
|
||||||
import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource;
|
import org.elasticsearch.xpack.watcher.support.xcontent.XContentSource;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||||
import static org.hamcrest.Matchers.hasEntry;
|
import static org.hamcrest.Matchers.hasEntry;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
import static org.hamcrest.core.Is.is;
|
import static org.hamcrest.core.Is.is;
|
||||||
import static org.mockito.Matchers.anyObject;
|
|
||||||
import static org.mockito.Matchers.eq;
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,23 +29,16 @@ import static org.mockito.Mockito.when;
|
||||||
public class WatcherFeatureSetTests extends ESTestCase {
|
public class WatcherFeatureSetTests extends ESTestCase {
|
||||||
|
|
||||||
private XPackLicenseState licenseState;
|
private XPackLicenseState licenseState;
|
||||||
private NamedWriteableRegistry namedWriteableRegistry;
|
|
||||||
private WatcherService watcherService;
|
private WatcherService watcherService;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() throws Exception {
|
public void init() throws Exception {
|
||||||
licenseState = mock(XPackLicenseState.class);
|
licenseState = mock(XPackLicenseState.class);
|
||||||
namedWriteableRegistry = mock(NamedWriteableRegistry.class);
|
|
||||||
watcherService = mock(WatcherService.class);
|
watcherService = mock(WatcherService.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testWritableRegistration() throws Exception {
|
|
||||||
new WatcherFeatureSet(Settings.EMPTY, licenseState, namedWriteableRegistry, watcherService);
|
|
||||||
verify(namedWriteableRegistry).register(eq(WatcherFeatureSet.Usage.class), eq("xpack.usage.watcher"), anyObject());
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testAvailable() throws Exception {
|
public void testAvailable() throws Exception {
|
||||||
WatcherFeatureSet featureSet = new WatcherFeatureSet(Settings.EMPTY, licenseState, namedWriteableRegistry, watcherService);
|
WatcherFeatureSet featureSet = new WatcherFeatureSet(Settings.EMPTY, licenseState, watcherService);
|
||||||
boolean available = randomBoolean();
|
boolean available = randomBoolean();
|
||||||
when(licenseState.isWatcherAllowed()).thenReturn(available);
|
when(licenseState.isWatcherAllowed()).thenReturn(available);
|
||||||
assertThat(featureSet.available(), is(available));
|
assertThat(featureSet.available(), is(available));
|
||||||
|
@ -65,7 +54,7 @@ public class WatcherFeatureSetTests extends ESTestCase {
|
||||||
} else {
|
} else {
|
||||||
settings.put("xpack.watcher.enabled", enabled);
|
settings.put("xpack.watcher.enabled", enabled);
|
||||||
}
|
}
|
||||||
WatcherFeatureSet featureSet = new WatcherFeatureSet(settings.build(), licenseState, namedWriteableRegistry, watcherService);
|
WatcherFeatureSet featureSet = new WatcherFeatureSet(settings.build(), licenseState, watcherService);
|
||||||
assertThat(featureSet.enabled(), is(enabled));
|
assertThat(featureSet.enabled(), is(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +63,7 @@ public class WatcherFeatureSetTests extends ESTestCase {
|
||||||
statsMap.put("foo", "bar");
|
statsMap.put("foo", "bar");
|
||||||
when(watcherService.usageStats()).thenReturn(statsMap);
|
when(watcherService.usageStats()).thenReturn(statsMap);
|
||||||
|
|
||||||
WatcherFeatureSet featureSet = new WatcherFeatureSet(Settings.EMPTY, licenseState, namedWriteableRegistry, watcherService);
|
WatcherFeatureSet featureSet = new WatcherFeatureSet(Settings.EMPTY, licenseState, watcherService);
|
||||||
XContentBuilder builder = jsonBuilder();
|
XContentBuilder builder = jsonBuilder();
|
||||||
featureSet.usage().toXContent(builder, ToXContent.EMPTY_PARAMS);
|
featureSet.usage().toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||||
|
|
||||||
|
|
|
@ -15,9 +15,9 @@ import org.elasticsearch.client.node.NodeClient;
|
||||||
import org.elasticsearch.cluster.ClusterState;
|
import org.elasticsearch.cluster.ClusterState;
|
||||||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver;
|
||||||
import org.elasticsearch.cluster.routing.IndexRoutingTable;
|
import org.elasticsearch.cluster.routing.IndexRoutingTable;
|
||||||
import org.elasticsearch.common.component.AbstractLifecycleComponent;
|
|
||||||
import org.elasticsearch.common.io.Streams;
|
import org.elasticsearch.common.io.Streams;
|
||||||
import org.elasticsearch.common.network.NetworkModule;
|
import org.elasticsearch.common.network.NetworkModule;
|
||||||
|
import org.elasticsearch.common.settings.ClusterSettings;
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.util.Callback;
|
import org.elasticsearch.common.util.Callback;
|
||||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
|
@ -598,10 +598,11 @@ public abstract class AbstractWatcherIntegrationTestCase extends ESIntegTestCase
|
||||||
assertThat("watcher should only run on the elected master node, but it is running on [" + running + "] nodes", running, equalTo(1));
|
assertThat("watcher should only run on the elected master node, but it is running on [" + running + "] nodes", running, equalTo(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class NoopEmailService extends AbstractLifecycleComponent implements EmailService {
|
public static class NoopEmailService extends EmailService {
|
||||||
|
|
||||||
public NoopEmailService() {
|
public NoopEmailService() {
|
||||||
super(Settings.EMPTY);
|
super(Settings.EMPTY, null,
|
||||||
|
new ClusterSettings(Settings.EMPTY, Collections.singleton(EmailService.EMAIL_ACCOUNT_SETTING)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -613,15 +614,6 @@ public abstract class AbstractWatcherIntegrationTestCase extends ESIntegTestCase
|
||||||
public EmailSent send(Email email, Authentication auth, Profile profile, String accountName) {
|
public EmailSent send(Email email, Authentication auth, Profile profile, String accountName) {
|
||||||
return new EmailSent(accountName, email);
|
return new EmailSent(accountName, email);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStart() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doStop() {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void doClose() {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class TimeWarp {
|
protected static class TimeWarp {
|
||||||
|
|
Loading…
Reference in New Issue