Skip to content

Don't allow null User.principal #51988

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

Merged
merged 2 commits into from
Feb 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;

/**
* An authenticated user
Expand Down Expand Up @@ -49,7 +50,7 @@ public User(String username, String[] roles, String fullName, String email, Map<

private User(String username, String[] roles, String fullName, String email, Map<String, Object> metadata, boolean enabled,
User authenticatedUser) {
this.username = username;
this.username = Objects.requireNonNull(username);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I hate when I get NPE in a line that has many calls and it's not quite obvious what threw it so I prefer to add a message to requireNotNull. Not sure if this is worth rerunning CI here though, just a suggestion

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this line doesn't have many calls. As long as you can link the stacktrace to the code it's clear why this line throughs NPE.

That said, I'd be happy for the whole codebase to have a forbidden API on requireNonNull without a message, but we don't.

this.roles = roles == null ? Strings.EMPTY_ARRAY : roles;
this.metadata = metadata == null ? Map.of() : metadata;
this.fullName = fullName;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,9 @@ public void testPolicyDoesNotMatchNullValuesInEvent() throws Exception {
// user field matches
assertTrue("Matches the user filter predicate.", auditTrail.eventFilterPolicyRegistry.ignorePredicate().test(
new AuditEventMetaInfo(Optional.of(randomFrom(filteredUsers)), Optional.empty(), Optional.empty(), Optional.empty())));
final User unfilteredUser;
final User unfilteredUser = mock(User.class);
if (randomBoolean()) {
unfilteredUser = new User(null);
} else {
unfilteredUser = new User(new User(null), new User(randomFrom(filteredUsers).principal()));
when(unfilteredUser.authenticatedUser()).thenReturn(new User(randomFrom(filteredUsernames)));
}
// null user field does NOT match
assertFalse("Does not match the user filter predicate because of null username.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.elasticsearch.xpack.core.security.authc.AuthenticationField;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.ingest.SetSecurityUserProcessor.Property;
import org.mockito.Mockito;

import java.util.Arrays;
import java.util.EnumSet;
Expand Down Expand Up @@ -42,15 +43,21 @@ public void testProcessor() throws Exception {
assertThat(result.get("email"), equalTo("_email"));
assertThat(((Map) result.get("metadata")).size(), equalTo(1));
assertThat(((Map) result.get("metadata")).get("key"), equalTo("value"));
}

// test when user holds no data:
threadContext = new ThreadContext(Settings.EMPTY);
user = new User(null, null, null);
threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));
ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>());
processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.allOf(Property.class));
public void testProcessorWithEmptyUserData() throws Exception {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(need a mock, because a real user cannot have a null username)

Could we just remove this test case instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only if we also removed the code from the security processor that deals with nulls.
Which I considered, but I was already down a rabbit hole.

What the processor is doing makes sense - it doesn't make any assumptions about whether a User can have a null principal (although it would also be fine for it to rely on it not being null), so I decided not to bite off even more right now, and just leave it.

// test when user returns null for all values (need a mock, because a real user cannot have a null username)
User user = Mockito.mock(User.class);
Authentication authentication = Mockito.mock(Authentication.class);
Mockito.when(authentication.getUser()).thenReturn(user);

ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, authentication);

IngestDocument ingestDocument = new IngestDocument(new HashMap<>(), new HashMap<>());
SetSecurityUserProcessor processor = new SetSecurityUserProcessor("_tag", threadContext, "_field", EnumSet.allOf(Property.class));
processor.execute(ingestDocument);
result = ingestDocument.getFieldValue("_field", Map.class);
Map result = ingestDocument.getFieldValue("_field", Map.class);
assertThat(result.size(), equalTo(0));
}

Expand Down Expand Up @@ -80,7 +87,7 @@ public void testUsernameProperties() throws Exception {

public void testRolesProperties() throws Exception {
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
User user = new User(null, "role1", "role2");
User user = new User(randomAlphaOfLengthBetween(4, 12), "role1", "role2");
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));

Expand All @@ -96,7 +103,7 @@ public void testRolesProperties() throws Exception {

public void testFullNameProperties() throws Exception {
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
User user = new User(null, null, "_full_name", null, Map.of(), true);
User user = new User(randomAlphaOfLengthBetween(4, 12), null, "_full_name", null, Map.of(), true);
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));

Expand All @@ -112,7 +119,7 @@ public void testFullNameProperties() throws Exception {

public void testEmailProperties() throws Exception {
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
User user = new User(null, null, null, "_email", Map.of(), true);
User user = new User(randomAlphaOfLengthBetween(4, 12), null, null, "_email", Map.of(), true);
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));

Expand All @@ -128,7 +135,7 @@ public void testEmailProperties() throws Exception {

public void testMetadataProperties() throws Exception {
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
User user = new User(null, null, null, null, Map.of("key", "value"), true);
User user = new User(randomAlphaOfLengthBetween(4, 12), null, null, null, Map.of("key", "value"), true);
Authentication.RealmRef realmRef = new Authentication.RealmRef("_name", "_type", "_node_name");
threadContext.putTransient(AuthenticationField.AUTHENTICATION_KEY, new Authentication(user, realmRef, null));

Expand Down