diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/user/PutUserRequest.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/user/PutUserRequest.java index cff1922825c..4cf63262fda 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/user/PutUserRequest.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/user/PutUserRequest.java @@ -8,7 +8,6 @@ package org.elasticsearch.xpack.security.action.user; import org.elasticsearch.action.ActionRequest; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.support.WriteRequest; -import org.elasticsearch.action.support.WriteRequest.RefreshPolicy; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; @@ -33,6 +32,7 @@ public class PutUserRequest extends ActionRequest implements Use private String email; private Map metadata; private char[] passwordHash; + private boolean enabled = true; private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE; public PutUserRequest() { @@ -79,6 +79,10 @@ public class PutUserRequest extends ActionRequest implements Use this.passwordHash = passwordHash; } + public boolean enabled() { + return enabled; + } + /** * Should this request trigger a refresh ({@linkplain RefreshPolicy#IMMEDIATE}, the default), wait for a refresh ( * {@linkplain RefreshPolicy#WAIT_UNTIL}), or proceed ignore refreshes entirely ({@linkplain RefreshPolicy#NONE}). @@ -119,6 +123,10 @@ public class PutUserRequest extends ActionRequest implements Use return passwordHash; } + public void enabled(boolean enabled) { + this.enabled = enabled; + } + @Override public String[] usernames() { return new String[] { username }; @@ -139,6 +147,7 @@ public class PutUserRequest extends ActionRequest implements Use email = in.readOptionalString(); metadata = in.readBoolean() ? in.readMap() : null; refreshPolicy = RefreshPolicy.readFrom(in); + enabled = in.readBoolean(); } @Override @@ -162,5 +171,6 @@ public class PutUserRequest extends ActionRequest implements Use out.writeMap(metadata); } refreshPolicy.writeTo(out); + out.writeBoolean(enabled); } } diff --git a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilder.java b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilder.java index 38888f19c00..9867d035644 100644 --- a/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilder.java +++ b/elasticsearch/x-pack/security/src/main/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilder.java @@ -16,6 +16,7 @@ import org.elasticsearch.common.ValidationException; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParser.Token; import org.elasticsearch.xpack.security.authc.support.Hasher; import org.elasticsearch.xpack.security.authc.support.SecuredString; import org.elasticsearch.xpack.security.support.Validation; @@ -84,6 +85,11 @@ public class PutUserRequestBuilder extends ActionRequestBuilder) () -> new ParameterizedMessage("unable to put user [{}]", request.username()), e); @@ -398,7 +398,8 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL User.Fields.ROLES.getPreferredName(), putUserRequest.roles(), User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(), User.Fields.EMAIL.getPreferredName(), putUserRequest.email(), - User.Fields.METADATA.getPreferredName(), putUserRequest.metadata()) + User.Fields.METADATA.getPreferredName(), putUserRequest.metadata(), + User.Fields.ENABLED.getPreferredName(), putUserRequest.enabled()) .setRefreshPolicy(putUserRequest.getRefreshPolicy()) .execute(new ActionListener() { @Override @@ -424,27 +425,21 @@ public class NativeUsersStore extends AbstractComponent implements ClusterStateL }); } - private void upsertUser(final PutUserRequest putUserRequest, final ActionListener listener) { + private void indexUser(final PutUserRequest putUserRequest, final ActionListener listener) { assert putUserRequest.passwordHash() != null; - client.prepareUpdate(SecurityTemplateService.SECURITY_INDEX_NAME, + client.prepareIndex(SecurityTemplateService.SECURITY_INDEX_NAME, USER_DOC_TYPE, putUserRequest.username()) - .setDoc(User.Fields.USERNAME.getPreferredName(), putUserRequest.username(), - User.Fields.PASSWORD.getPreferredName(), String.valueOf(putUserRequest.passwordHash()), - User.Fields.ROLES.getPreferredName(), putUserRequest.roles(), - User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(), - User.Fields.EMAIL.getPreferredName(), putUserRequest.email(), - User.Fields.METADATA.getPreferredName(), putUserRequest.metadata()) - .setUpsert(User.Fields.USERNAME.getPreferredName(), putUserRequest.username(), + .setSource(User.Fields.USERNAME.getPreferredName(), putUserRequest.username(), User.Fields.PASSWORD.getPreferredName(), String.valueOf(putUserRequest.passwordHash()), User.Fields.ROLES.getPreferredName(), putUserRequest.roles(), User.Fields.FULL_NAME.getPreferredName(), putUserRequest.fullName(), User.Fields.EMAIL.getPreferredName(), putUserRequest.email(), User.Fields.METADATA.getPreferredName(), putUserRequest.metadata(), - User.Fields.ENABLED.getPreferredName(), true) + User.Fields.ENABLED.getPreferredName(), putUserRequest.enabled()) .setRefreshPolicy(putUserRequest.getRefreshPolicy()) - .execute(new ActionListener() { + .execute(new ActionListener() { @Override - public void onResponse(UpdateResponse updateResponse) { + public void onResponse(IndexResponse updateResponse) { clearRealmCache(putUserRequest.username(), listener, updateResponse.getResult() == DocWriteResponse.Result.CREATED); } diff --git a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilderTests.java b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilderTests.java index e049dac1d4c..14724e09b6b 100644 --- a/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilderTests.java +++ b/elasticsearch/x-pack/security/src/test/java/org/elasticsearch/xpack/security/action/user/PutUserRequestBuilderTests.java @@ -40,6 +40,7 @@ public class PutUserRequestBuilderTests extends ESTestCase { assertThat(request.fullName(), nullValue()); assertThat(request.email(), nullValue()); assertThat(request.metadata().isEmpty(), is(true)); + assertTrue(request.enabled()); } public void testMissingEmailFullName() throws Exception { @@ -113,4 +114,20 @@ public class PutUserRequestBuilderTests extends ESTestCase { () -> builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8)))); assertThat(e.getMessage(), containsString("expected field [email] to be of type string")); } + + public void testWithEnabled() throws IOException { + final String json = "{\n" + + " \"roles\": [\n" + + " \"kibana4\"\n" + + " ],\n" + + " \"full_name\": \"Kibana User\",\n" + + " \"email\": \"kibana@elastic.co\",\n" + + " \"metadata\": {}\n," + + " \"enabled\": false\n" + + "}"; + + PutUserRequestBuilder builder = new PutUserRequestBuilder(mock(Client.class)); + PutUserRequest request = builder.source("kibana4", new BytesArray(json.getBytes(StandardCharsets.UTF_8))).request(); + assertFalse(request.enabled()); + } } diff --git a/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/users/10_basic.yaml b/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/users/10_basic.yaml index 1e18f51ac13..fda190e590f 100644 --- a/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/users/10_basic.yaml +++ b/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/users/10_basic.yaml @@ -47,3 +47,57 @@ teardown: - match: { joe.email: "joe@bazooka.gum" } - match: { joe.metadata.key1: "val1" } - match: { joe.metadata.key2: "val2" } + +--- +"Test put user with username in body": + - do: + xpack.security.put_user: + username: "joe" + body: > + { + "username": "joe", + "password" : "s3krit", + "roles" : [ "superuser" ], + "full_name" : "Bazooka Joe", + "email" : "joe@bazooka.gum", + "metadata" : { + "key1" : "val1", + "key2" : "val2" + } + } + - match: { user: { created: true } } + + - do: + headers: + Authorization: "Basic am9lOnMza3JpdA==" + cluster.health: {} + - match: { timed_out: false } + + - do: + xpack.security.get_user: + username: "joe" + - match: { joe.username: "joe" } + - match: { joe.roles.0: "superuser" } + - match: { joe.full_name: "Bazooka Joe" } + - match: { joe.email: "joe@bazooka.gum" } + - match: { joe.metadata.key1: "val1" } + - match: { joe.metadata.key2: "val2" } + +--- +"Test put user with different username in body": + - do: + catch: request + xpack.security.put_user: + username: "joe" + body: > + { + "username": "joey", + "password" : "s3krit", + "roles" : [ "superuser" ], + "full_name" : "Bazooka Joe", + "email" : "joe@bazooka.gum", + "metadata" : { + "key1" : "val1", + "key2" : "val2" + } + } diff --git a/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/users/31_create_disabled.yaml b/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/users/31_create_disabled.yaml new file mode 100644 index 00000000000..c2ca1c4b343 --- /dev/null +++ b/elasticsearch/x-pack/security/src/test/resources/rest-api-spec/test/users/31_create_disabled.yaml @@ -0,0 +1,44 @@ +--- +setup: + - skip: + features: [headers, catch_unauthorized] + - do: + cluster.health: + wait_for_status: yellow + + - do: + xpack.security.put_user: + username: "joe" + body: > + { + "password": "s3krit", + "roles" : [ "superuser" ], + "enabled": false + } +--- +teardown: + - do: + xpack.security.delete_user: + username: "joe" + ignore: 404 + +--- +"Test disable then enable user": +# validate user cannot login + - do: + catch: unauthorized + headers: + Authorization: "Basic am9lOnMza3JpdA==" + cluster.health: {} + +# enable + - do: + xpack.security.enable_user: + username: "joe" + +# validate user can login + - do: + headers: + Authorization: "Basic am9lOnMza3JpdA==" + cluster.health: {} + - match: { timed_out: false }