-
Notifications
You must be signed in to change notification settings - Fork 25k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Report anonymous roles in authenticate response #61355
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,18 +17,24 @@ | |
import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequest; | ||
import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse; | ||
import org.elasticsearch.xpack.core.security.authc.Authentication; | ||
import org.elasticsearch.xpack.core.security.user.AnonymousUser; | ||
import org.elasticsearch.xpack.core.security.user.SystemUser; | ||
import org.elasticsearch.xpack.core.security.user.User; | ||
import org.elasticsearch.xpack.core.security.user.XPackUser; | ||
|
||
import java.util.stream.Stream; | ||
|
||
public class TransportAuthenticateAction extends HandledTransportAction<AuthenticateRequest, AuthenticateResponse> { | ||
|
||
private final SecurityContext securityContext; | ||
private final AnonymousUser anonymousUser; | ||
|
||
@Inject | ||
public TransportAuthenticateAction(TransportService transportService, ActionFilters actionFilters, SecurityContext securityContext) { | ||
public TransportAuthenticateAction(TransportService transportService, ActionFilters actionFilters, SecurityContext securityContext, | ||
AnonymousUser anonymousUser) { | ||
super(AuthenticateAction.NAME, transportService, actionFilters, AuthenticateRequest::new); | ||
this.securityContext = securityContext; | ||
this.anonymousUser = anonymousUser; | ||
} | ||
|
||
@Override | ||
|
@@ -43,7 +49,32 @@ protected void doExecute(Task task, AuthenticateRequest request, ActionListener< | |
} else if (SystemUser.is(runAsUser) || XPackUser.is(runAsUser)) { | ||
listener.onFailure(new IllegalArgumentException("user [" + runAsUser.principal() + "] is internal")); | ||
} else { | ||
listener.onResponse(new AuthenticateResponse(authentication)); | ||
final User user = authentication.getUser(); | ||
final boolean shouldAddAnonymousRoleNames = anonymousUser.enabled() && false == anonymousUser.equals(user) | ||
&& authentication.getAuthenticationType() != Authentication.AuthenticationType.API_KEY; | ||
if (shouldAddAnonymousRoleNames) { | ||
final String[] allRoleNames = Stream.concat( | ||
Stream.of(user.roles()), Stream.of(anonymousUser.roles())).toArray(String[]::new); | ||
listener.onResponse(new AuthenticateResponse( | ||
new Authentication( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is rather verbose but I don't have any good suggestions to do otherwise and we don't seem to have a generic need for a cloneWithChanges method ! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I initially had this logic inside the |
||
new User(new User( | ||
user.principal(), | ||
allRoleNames, | ||
user.fullName(), | ||
user.email(), | ||
user.metadata(), | ||
user.enabled() | ||
), user.authenticatedUser()), | ||
authentication.getAuthenticatedBy(), | ||
authentication.getLookedUpBy(), | ||
authentication.getVersion(), | ||
authentication.getAuthenticationType(), | ||
authentication.getMetadata() | ||
) | ||
)); | ||
} else { | ||
listener.onResponse(new AuthenticateResponse(authentication)); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,13 +17,15 @@ | |
import org.elasticsearch.xpack.core.security.action.user.AuthenticateRequest; | ||
import org.elasticsearch.xpack.core.security.action.user.AuthenticateResponse; | ||
import org.elasticsearch.xpack.core.security.authc.Authentication; | ||
import org.elasticsearch.xpack.core.security.user.AnonymousUser; | ||
import org.elasticsearch.xpack.core.security.user.ElasticUser; | ||
import org.elasticsearch.xpack.core.security.user.KibanaUser; | ||
import org.elasticsearch.xpack.core.security.user.SystemUser; | ||
import org.elasticsearch.xpack.core.security.user.User; | ||
import org.elasticsearch.xpack.core.security.user.XPackUser; | ||
|
||
import java.util.Collections; | ||
import java.util.List; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
|
||
import static org.hamcrest.Matchers.containsString; | ||
|
@@ -44,7 +46,7 @@ public void testInternalUser() { | |
TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, | ||
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); | ||
TransportAuthenticateAction action = new TransportAuthenticateAction(transportService, | ||
mock(ActionFilters.class), securityContext); | ||
mock(ActionFilters.class), securityContext, prepareAnonymousUser()); | ||
|
||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>(); | ||
final AtomicReference<AuthenticateResponse> responseRef = new AtomicReference<>(); | ||
|
@@ -70,7 +72,7 @@ public void testNullUser() { | |
TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, | ||
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); | ||
TransportAuthenticateAction action = new TransportAuthenticateAction(transportService, | ||
mock(ActionFilters.class), securityContext); | ||
mock(ActionFilters.class), securityContext, prepareAnonymousUser()); | ||
|
||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>(); | ||
final AtomicReference<AuthenticateResponse> responseRef = new AtomicReference<>(); | ||
|
@@ -98,10 +100,12 @@ public void testValidAuthentication(){ | |
SecurityContext securityContext = mock(SecurityContext.class); | ||
when(securityContext.getAuthentication()).thenReturn(authentication); | ||
when(securityContext.getUser()).thenReturn(user); | ||
|
||
final AnonymousUser anonymousUser = prepareAnonymousUser(); | ||
TransportService transportService = new TransportService(Settings.EMPTY, mock(Transport.class), null, | ||
TransportService.NOOP_TRANSPORT_INTERCEPTOR, x -> null, null, Collections.emptySet()); | ||
TransportAuthenticateAction action = new TransportAuthenticateAction(transportService, | ||
mock(ActionFilters.class), securityContext); | ||
mock(ActionFilters.class), securityContext, anonymousUser); | ||
|
||
final AtomicReference<Throwable> throwableRef = new AtomicReference<>(); | ||
final AtomicReference<AuthenticateResponse> responseRef = new AtomicReference<>(); | ||
|
@@ -118,7 +122,33 @@ public void onFailure(Exception e) { | |
}); | ||
|
||
assertThat(responseRef.get(), notNullValue()); | ||
assertThat(responseRef.get().authentication(), sameInstance(authentication)); | ||
if (anonymousUser.enabled()) { | ||
final Authentication auth = responseRef.get().authentication(); | ||
final User authUser = auth.getUser(); | ||
List.of(authUser.roles()).containsAll(List.of(authentication.getUser().roles())); | ||
List.of(authUser.roles()).containsAll(List.of(anonymousUser.roles())); | ||
assertThat(authUser.authenticatedUser(), sameInstance(user.authenticatedUser())); | ||
assertThat(auth.getAuthenticatedBy(), sameInstance(auth.getAuthenticatedBy())); | ||
assertThat(auth.getLookedUpBy(), sameInstance(auth.getLookedUpBy())); | ||
assertThat(auth.getVersion(), sameInstance(auth.getVersion())); | ||
assertThat(auth.getAuthenticationType(), sameInstance(auth.getAuthenticationType())); | ||
assertThat(auth.getMetadata(), sameInstance(auth.getMetadata())); | ||
} else { | ||
assertThat(responseRef.get().authentication(), sameInstance(authentication)); | ||
} | ||
assertThat(throwableRef.get(), nullValue()); | ||
} | ||
|
||
private AnonymousUser prepareAnonymousUser() { | ||
final AnonymousUser anonymousUser = mock(AnonymousUser.class); | ||
if (randomBoolean()) { | ||
when(anonymousUser.enabled()).thenReturn(true); | ||
} else { | ||
when(anonymousUser.enabled()).thenReturn(false); | ||
} | ||
final String[] roleNames = randomList(1, 4, () -> randomAlphaOfLengthBetween(4, 12)).toArray(new String[0]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: this only makes sense in the if branch right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You right. Thanks |
||
when(anonymousUser.roles()).thenReturn(roleNames); | ||
return anonymousUser; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: add a hint that anonymous user is enabled maybe so the test makes more sense when you look at it ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a comment about anonymous access is configured in build.gradle