Handle missing user in user privilege APIs (#34575)

For user/_has_privileges and user/_privileges, handle the case where
there is no user in the security context. This is likely to indicate
that the server is running with a basic license, in which case the
action will be rejected with a non-compliance exception (provided
we don't throw a NPE).

The implementation here is based on the _authenticate API.

Resolves: #34567
This commit is contained in:
Tim Vernum 2018-10-19 17:54:01 +11:00 committed by GitHub
parent 56d4f69718
commit 670ccfb853
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 85 additions and 6 deletions

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.xpack.security.rest.action.user;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
@ -24,6 +25,7 @@ import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import java.io.IOException;
@ -52,7 +54,11 @@ public class RestGetUserPrivilegesAction extends SecurityBaseRestHandler {
@Override
public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
final String username = securityContext.getUser().principal();
final User user = securityContext.getUser();
if (user == null) {
return restChannel -> { throw new ElasticsearchSecurityException("there is no authenticated user"); };
}
final String username = user.principal();
final GetUserPrivilegesRequestBuilder requestBuilder = new SecurityClient(client).prepareGetUserPrivileges(username);
return channel -> requestBuilder.execute(new RestListener(channel));
}

View File

@ -5,6 +5,7 @@
*/
package org.elasticsearch.xpack.security.rest.action.user;
import org.elasticsearch.ElasticsearchSecurityException;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.Tuple;
@ -24,6 +25,7 @@ import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesRequestBui
import org.elasticsearch.xpack.core.security.action.user.HasPrivilegesResponse;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
import org.elasticsearch.xpack.core.security.client.SecurityClient;
import org.elasticsearch.xpack.core.security.user.User;
import org.elasticsearch.xpack.security.rest.action.SecurityBaseRestHandler;
import java.io.IOException;
@ -59,6 +61,9 @@ public class RestHasPrivilegesAction extends SecurityBaseRestHandler {
@Override
public RestChannelConsumer innerPrepareRequest(RestRequest request, NodeClient client) throws IOException {
final String username = getUsername(request);
if (username == null) {
return restChannel -> { throw new ElasticsearchSecurityException("there is no authenticated user"); };
}
final Tuple<XContentType, BytesReference> content = request.contentOrSourceParam();
HasPrivilegesRequestBuilder requestBuilder = new SecurityClient(client).prepareHasPrivileges(username, content.v2(), content.v1());
return channel -> requestBuilder.execute(new HasPrivilegesRestResponseBuilder(username, channel));
@ -69,7 +74,11 @@ public class RestHasPrivilegesAction extends SecurityBaseRestHandler {
if (username != null) {
return username;
}
return securityContext.getUser().principal();
final User user = securityContext.getUser();
if (user == null) {
return null;
}
return user.principal();
}
static class HasPrivilegesRestResponseBuilder extends RestBuilderListener<HasPrivilegesResponse> {

View File

@ -6,17 +6,24 @@
package org.elasticsearch.xpack.security.rest.action.user;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.set.Sets;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.rest.FakeRestChannel;
import org.elasticsearch.test.rest.FakeRestRequest;
import org.elasticsearch.xpack.core.security.SecurityContext;
import org.elasticsearch.xpack.core.security.action.user.GetUserPrivilegesResponse;
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor.ApplicationResourcePrivileges;
import org.elasticsearch.xpack.core.security.authz.permission.FieldPermissionsDefinition;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivilege;
import org.elasticsearch.xpack.core.security.authz.privilege.ConditionalClusterPrivileges;
import org.hamcrest.Matchers;
import java.util.Arrays;
import java.util.Collections;
@ -24,9 +31,27 @@ import java.util.LinkedHashSet;
import java.util.Set;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class RestGetUserPrivilegesActionTests extends ESTestCase {
public void testBasicLicense() throws Exception {
final XPackLicenseState licenseState = mock(XPackLicenseState.class);
final RestGetUserPrivilegesAction action = new RestGetUserPrivilegesAction(Settings.EMPTY, mock(RestController.class),
mock(SecurityContext.class), licenseState);
when(licenseState.isSecurityAvailable()).thenReturn(false);
final FakeRestRequest request = new FakeRestRequest();
final FakeRestChannel channel = new FakeRestChannel(request, true, 1);
action.handleRequest(request, channel, mock(NodeClient.class));
assertThat(channel.capturedResponse(), notNullValue());
assertThat(channel.capturedResponse().status(), equalTo(RestStatus.FORBIDDEN));
assertThat(channel.capturedResponse().content().utf8ToString(), containsString("current license is non-compliant for [security]"));
}
public void testBuildResponse() throws Exception {
final RestGetUserPrivilegesAction.RestListener listener = new RestGetUserPrivilegesAction.RestListener(null);
@ -37,8 +62,8 @@ public class RestGetUserPrivilegesActionTests extends ESTestCase {
final Set<GetUserPrivilegesResponse.Indices> index = new LinkedHashSet<>(Arrays.asList(
new GetUserPrivilegesResponse.Indices(Arrays.asList("index-1", "index-2", "index-3-*"), Arrays.asList("read", "write"),
new LinkedHashSet<>(Arrays.asList(
new FieldPermissionsDefinition.FieldGrantExcludeGroup(new String[]{ "public.*" }, new String[0]),
new FieldPermissionsDefinition.FieldGrantExcludeGroup(new String[]{ "*" }, new String[]{ "private.*" })
new FieldPermissionsDefinition.FieldGrantExcludeGroup(new String[]{"public.*"}, new String[0]),
new FieldPermissionsDefinition.FieldGrantExcludeGroup(new String[]{"*"}, new String[]{"private.*"})
)),
new LinkedHashSet<>(Arrays.asList(
new BytesArray("{ \"term\": { \"access\": \"public\" } }"),
@ -60,7 +85,7 @@ public class RestGetUserPrivilegesActionTests extends ESTestCase {
listener.buildResponse(response, builder);
String json = Strings.toString(builder);
assertThat(json, Matchers.equalTo("{" +
assertThat(json, equalTo("{" +
"\"cluster\":[\"monitor\",\"manage_ml\",\"manage_watcher\"]," +
"\"global\":[" +
"{\"application\":{\"manage\":{\"applications\":[\"app01\",\"app02\"]}}}" +

View File

@ -0,0 +1,39 @@
/*
* 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.security.rest.action.user;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.license.XPackLicenseState;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.test.rest.FakeRestChannel;
import org.elasticsearch.test.rest.FakeRestRequest;
import org.elasticsearch.xpack.core.security.SecurityContext;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class RestHasPrivilegesActionTests extends ESTestCase {
public void testBasicLicense() throws Exception {
final XPackLicenseState licenseState = mock(XPackLicenseState.class);
final RestHasPrivilegesAction action = new RestHasPrivilegesAction(Settings.EMPTY, mock(RestController.class),
mock(SecurityContext.class), licenseState);
when(licenseState.isSecurityAvailable()).thenReturn(false);
final FakeRestRequest request = new FakeRestRequest();
final FakeRestChannel channel = new FakeRestChannel(request, true, 1);
action.handleRequest(request, channel, mock(NodeClient.class));
assertThat(channel.capturedResponse(), notNullValue());
assertThat(channel.capturedResponse().status(), equalTo(RestStatus.FORBIDDEN));
assertThat(channel.capturedResponse().content().utf8ToString(), containsString("current license is non-compliant for [security]"));
}
}