|
@@ -22,6 +22,7 @@ import org.elasticsearch.action.support.PlainActionFuture;
|
|
|
import org.elasticsearch.action.update.UpdateAction;
|
|
|
import org.elasticsearch.action.update.UpdateRequestBuilder;
|
|
|
import org.elasticsearch.client.Client;
|
|
|
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
|
|
import org.elasticsearch.cluster.service.ClusterService;
|
|
|
import org.elasticsearch.common.SuppressForbidden;
|
|
|
import org.elasticsearch.common.collect.Tuple;
|
|
@@ -103,6 +104,7 @@ import static org.mockito.Mockito.doThrow;
|
|
|
import static org.mockito.Mockito.mock;
|
|
|
import static org.mockito.Mockito.never;
|
|
|
import static org.mockito.Mockito.reset;
|
|
|
+import static org.mockito.Mockito.times;
|
|
|
import static org.mockito.Mockito.verify;
|
|
|
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|
|
import static org.mockito.Mockito.verifyZeroInteractions;
|
|
@@ -133,6 +135,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
@SuppressForbidden(reason = "Allow accessing localhost")
|
|
|
public void init() throws Exception {
|
|
|
token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
message = new InternalMessage();
|
|
|
remoteAddress = new InetSocketAddress(InetAddress.getLocalHost(), 100);
|
|
|
message.remoteAddress(new TransportAddress(remoteAddress));
|
|
@@ -258,6 +261,134 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
verify(auditTrail).authenticationFailed(reqId, firstRealm.name(), token, "_action", message);
|
|
|
}
|
|
|
|
|
|
+ public void testAuthenticateSmartRealmOrdering() {
|
|
|
+ User user = new User("_username", "r1");
|
|
|
+ when(firstRealm.supports(token)).thenReturn(true);
|
|
|
+ mockAuthenticate(firstRealm, token, null);
|
|
|
+ when(secondRealm.supports(token)).thenReturn(true);
|
|
|
+ mockAuthenticate(secondRealm, token, user);
|
|
|
+ when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
+ final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
|
|
|
+
|
|
|
+ final AtomicBoolean completed = new AtomicBoolean(false);
|
|
|
+ service.authenticate("_action", message, (User)null, ActionListener.wrap(result -> {
|
|
|
+ assertThat(result, notNullValue());
|
|
|
+ assertThat(result.getUser(), is(user));
|
|
|
+ assertThat(result.getLookedUpBy(), is(nullValue()));
|
|
|
+ assertThat(result.getAuthenticatedBy(), is(notNullValue())); // TODO implement equals
|
|
|
+ assertThreadContextContainsAuthentication(result);
|
|
|
+ setCompletedToTrue(completed);
|
|
|
+ }, this::logAndFail));
|
|
|
+ assertTrue(completed.get());
|
|
|
+
|
|
|
+ completed.set(false);
|
|
|
+ service.authenticate("_action", message, (User)null, ActionListener.wrap(result -> {
|
|
|
+ assertThat(result, notNullValue());
|
|
|
+ assertThat(result.getUser(), is(user));
|
|
|
+ assertThat(result.getLookedUpBy(), is(nullValue()));
|
|
|
+ assertThat(result.getAuthenticatedBy(), is(notNullValue())); // TODO implement equals
|
|
|
+ assertThreadContextContainsAuthentication(result);
|
|
|
+ setCompletedToTrue(completed);
|
|
|
+ }, this::logAndFail));
|
|
|
+ verify(auditTrail).authenticationFailed(reqId, firstRealm.name(), token, "_action", message);
|
|
|
+ verify(auditTrail, times(2)).authenticationSuccess(reqId, secondRealm.name(), user, "_action", message);
|
|
|
+ verify(firstRealm, times(2)).name(); // used above one time
|
|
|
+ verify(secondRealm, times(3)).name(); // used above one time
|
|
|
+ verify(secondRealm, times(2)).type(); // used to create realm ref
|
|
|
+ verify(firstRealm, times(2)).token(threadContext);
|
|
|
+ verify(secondRealm, times(2)).token(threadContext);
|
|
|
+ verify(firstRealm).supports(token);
|
|
|
+ verify(secondRealm, times(2)).supports(token);
|
|
|
+ verify(firstRealm).authenticate(eq(token), any(ActionListener.class));
|
|
|
+ verify(secondRealm, times(2)).authenticate(eq(token), any(ActionListener.class));
|
|
|
+ verifyNoMoreInteractions(auditTrail, firstRealm, secondRealm);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testCacheClearOnSecurityIndexChange() {
|
|
|
+ long expectedInvalidation = 0L;
|
|
|
+ assertEquals(expectedInvalidation, service.getNumInvalidation());
|
|
|
+
|
|
|
+ // existing to no longer present
|
|
|
+ SecurityIndexManager.State previousState = dummyState(randomFrom(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW));
|
|
|
+ SecurityIndexManager.State currentState = dummyState(null);
|
|
|
+ service.onSecurityIndexStateChange(previousState, currentState);
|
|
|
+ assertEquals(++expectedInvalidation, service.getNumInvalidation());
|
|
|
+
|
|
|
+ // doesn't exist to exists
|
|
|
+ previousState = dummyState(null);
|
|
|
+ currentState = dummyState(randomFrom(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW));
|
|
|
+ service.onSecurityIndexStateChange(previousState, currentState);
|
|
|
+ assertEquals(++expectedInvalidation, service.getNumInvalidation());
|
|
|
+
|
|
|
+ // green or yellow to red
|
|
|
+ previousState = dummyState(randomFrom(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW));
|
|
|
+ currentState = dummyState(ClusterHealthStatus.RED);
|
|
|
+ service.onSecurityIndexStateChange(previousState, currentState);
|
|
|
+ assertEquals(expectedInvalidation, service.getNumInvalidation());
|
|
|
+
|
|
|
+ // red to non red
|
|
|
+ previousState = dummyState(ClusterHealthStatus.RED);
|
|
|
+ currentState = dummyState(randomFrom(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW));
|
|
|
+ service.onSecurityIndexStateChange(previousState, currentState);
|
|
|
+ assertEquals(++expectedInvalidation, service.getNumInvalidation());
|
|
|
+
|
|
|
+ // green to yellow or yellow to green
|
|
|
+ previousState = dummyState(randomFrom(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW));
|
|
|
+ currentState = dummyState(previousState.indexStatus == ClusterHealthStatus.GREEN ?
|
|
|
+ ClusterHealthStatus.YELLOW : ClusterHealthStatus.GREEN);
|
|
|
+ service.onSecurityIndexStateChange(previousState, currentState);
|
|
|
+ assertEquals(expectedInvalidation, service.getNumInvalidation());
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testAuthenticateSmartRealmOrderingDisabled() {
|
|
|
+ final Settings settings = Settings.builder()
|
|
|
+ .put(AuthenticationService.SUCCESS_AUTH_CACHE_ENABLED.getKey(), false)
|
|
|
+ .build();
|
|
|
+ service = new AuthenticationService(settings, realms, auditTrail,
|
|
|
+ new DefaultAuthenticationFailureHandler(Collections.emptyMap()), threadPool, new AnonymousUser(Settings.EMPTY),
|
|
|
+ tokenService);
|
|
|
+ User user = new User("_username", "r1");
|
|
|
+ when(firstRealm.supports(token)).thenReturn(true);
|
|
|
+ mockAuthenticate(firstRealm, token, null);
|
|
|
+ when(secondRealm.supports(token)).thenReturn(true);
|
|
|
+ mockAuthenticate(secondRealm, token, user);
|
|
|
+ when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
+ final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
|
|
|
+
|
|
|
+ final AtomicBoolean completed = new AtomicBoolean(false);
|
|
|
+ service.authenticate("_action", message, (User)null, ActionListener.wrap(result -> {
|
|
|
+ assertThat(result, notNullValue());
|
|
|
+ assertThat(result.getUser(), is(user));
|
|
|
+ assertThat(result.getLookedUpBy(), is(nullValue()));
|
|
|
+ assertThat(result.getAuthenticatedBy(), is(notNullValue())); // TODO implement equals
|
|
|
+ assertThreadContextContainsAuthentication(result);
|
|
|
+ setCompletedToTrue(completed);
|
|
|
+ }, this::logAndFail));
|
|
|
+ assertTrue(completed.get());
|
|
|
+
|
|
|
+ completed.set(false);
|
|
|
+ service.authenticate("_action", message, (User)null, ActionListener.wrap(result -> {
|
|
|
+ assertThat(result, notNullValue());
|
|
|
+ assertThat(result.getUser(), is(user));
|
|
|
+ assertThat(result.getLookedUpBy(), is(nullValue()));
|
|
|
+ assertThat(result.getAuthenticatedBy(), is(notNullValue())); // TODO implement equals
|
|
|
+ assertThreadContextContainsAuthentication(result);
|
|
|
+ setCompletedToTrue(completed);
|
|
|
+ }, this::logAndFail));
|
|
|
+ verify(auditTrail, times(2)).authenticationFailed(reqId, firstRealm.name(), token, "_action", message);
|
|
|
+ verify(auditTrail, times(2)).authenticationSuccess(reqId, secondRealm.name(), user, "_action", message);
|
|
|
+ verify(firstRealm, times(3)).name(); // used above one time
|
|
|
+ verify(secondRealm, times(3)).name(); // used above one time
|
|
|
+ verify(secondRealm, times(2)).type(); // used to create realm ref
|
|
|
+ verify(firstRealm, times(2)).token(threadContext);
|
|
|
+ verify(secondRealm, times(2)).token(threadContext);
|
|
|
+ verify(firstRealm, times(2)).supports(token);
|
|
|
+ verify(secondRealm, times(2)).supports(token);
|
|
|
+ verify(firstRealm, times(2)).authenticate(eq(token), any(ActionListener.class));
|
|
|
+ verify(secondRealm, times(2)).authenticate(eq(token), any(ActionListener.class));
|
|
|
+ verifyNoMoreInteractions(auditTrail, firstRealm, secondRealm);
|
|
|
+ }
|
|
|
+
|
|
|
public void testAuthenticateFirstNotSupportingSecondSucceeds() throws Exception {
|
|
|
User user = new User("_username", "r1");
|
|
|
when(firstRealm.supports(token)).thenReturn(false);
|
|
@@ -614,6 +745,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testRealmSupportsMethodThrowingException() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
when(secondRealm.supports(token)).thenThrow(authenticationError("realm doesn't like supports"));
|
|
|
final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
|
|
@@ -628,6 +760,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testRealmSupportsMethodThrowingExceptionRest() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
when(secondRealm.supports(token)).thenThrow(authenticationError("realm doesn't like supports"));
|
|
|
try {
|
|
@@ -643,6 +776,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
public void testRealmAuthenticateTerminatingAuthenticationProcess() throws Exception {
|
|
|
final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
|
|
|
final AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
when(secondRealm.supports(token)).thenReturn(true);
|
|
|
final boolean terminateWithNoException = rarely();
|
|
@@ -684,6 +818,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testRealmAuthenticateThrowingException() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
when(secondRealm.supports(token)).thenReturn(true);
|
|
|
doThrow(authenticationError("realm doesn't like authenticate"))
|
|
@@ -700,6 +835,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testRealmAuthenticateThrowingExceptionRest() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
when(secondRealm.supports(token)).thenReturn(true);
|
|
|
doThrow(authenticationError("realm doesn't like authenticate"))
|
|
@@ -716,6 +852,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testRealmLookupThrowingException() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "run_as");
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
when(secondRealm.supports(token)).thenReturn(true);
|
|
@@ -736,6 +873,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testRealmLookupThrowingExceptionRest() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "run_as");
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
when(secondRealm.supports(token)).thenReturn(true);
|
|
@@ -755,6 +893,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testRunAsLookupSameRealm() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "run_as");
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
when(secondRealm.supports(token)).thenReturn(true);
|
|
@@ -803,6 +942,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
@SuppressWarnings("unchecked")
|
|
|
public void testRunAsLookupDifferentRealm() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "run_as");
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
when(secondRealm.supports(token)).thenReturn(true);
|
|
@@ -839,6 +979,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testRunAsWithEmptyRunAsUsernameRest() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
User user = new User("lookup user", new String[]{"user"});
|
|
|
threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "");
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
@@ -857,6 +998,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testRunAsWithEmptyRunAsUsername() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
User user = new User("lookup user", new String[]{"user"});
|
|
|
threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "");
|
|
|
final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
|
|
@@ -876,6 +1018,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
@SuppressWarnings("unchecked")
|
|
|
public void testAuthenticateTransportDisabledRunAsUser() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "run_as");
|
|
|
final String reqId = AuditUtil.getOrGenerateRequestId(threadContext);
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
@@ -897,6 +1040,7 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
|
|
|
public void testAuthenticateRestDisabledRunAsUser() throws Exception {
|
|
|
AuthenticationToken token = mock(AuthenticationToken.class);
|
|
|
+ when(token.principal()).thenReturn(randomAlphaOfLength(5));
|
|
|
threadContext.putHeader(AuthenticationServiceField.RUN_AS_USER_HEADER, "run_as");
|
|
|
when(secondRealm.token(threadContext)).thenReturn(token);
|
|
|
when(secondRealm.supports(token)).thenReturn(true);
|
|
@@ -1117,4 +1261,8 @@ public class AuthenticationServiceTests extends ESTestCase {
|
|
|
private void setCompletedToTrue(AtomicBoolean completed) {
|
|
|
assertTrue(completed.compareAndSet(false, true));
|
|
|
}
|
|
|
+
|
|
|
+ private SecurityIndexManager.State dummyState(ClusterHealthStatus indexStatus) {
|
|
|
+ return new SecurityIndexManager.State(true, true, true, true, null, indexStatus);
|
|
|
+ }
|
|
|
}
|