-
Notifications
You must be signed in to change notification settings - Fork 24.7k
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
HLRC: add change password API support #33509
Changes from 4 commits
ebc99c9
397a019
931ec38
168fe4f
54830e8
dcb091d
b2bbed7
2d45731
6c19675
e5404d0
7578bd1
5eaaa42
1cbd8a7
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 |
---|---|---|
|
@@ -20,8 +20,10 @@ | |
package org.elasticsearch.client; | ||
|
||
import org.elasticsearch.action.ActionListener; | ||
import org.elasticsearch.client.security.EmptyResponse; | ||
import org.elasticsearch.client.security.PutUserRequest; | ||
import org.elasticsearch.client.security.PutUserResponse; | ||
import org.elasticsearch.client.security.ChangePasswordRequest; | ||
|
||
import java.io.IOException; | ||
|
||
|
@@ -44,6 +46,7 @@ public final class SecurityClient { | |
* Create/update a user in the native realm synchronously. | ||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html"> | ||
* the docs</a> for more. | ||
* | ||
* @param request the request with the user's information | ||
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized | ||
* @return the response from the put user call | ||
|
@@ -58,12 +61,44 @@ public PutUserResponse putUser(PutUserRequest request, RequestOptions options) t | |
* Asynchronously create/update a user in the native realm. | ||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-users.html"> | ||
* the docs</a> for more. | ||
* @param request the request with the user's information | ||
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized | ||
* | ||
* @param request the request with the user's information | ||
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized | ||
* @param listener the listener to be notified upon request completion | ||
*/ | ||
public void putUserAsync(PutUserRequest request, RequestOptions options, ActionListener<PutUserResponse> listener) { | ||
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::putUser, options, | ||
PutUserResponse::fromXContent, listener, emptySet()); | ||
} | ||
|
||
/** | ||
* Change the password of a user in the native realm synchronously. | ||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-change-password.html"> | ||
* the docs</a> for more. | ||
* | ||
* @param request the request with the user's new password | ||
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized | ||
* @return the response from the change user password call | ||
* @throws IOException in case there is a problem sending the request or parsing back the response | ||
*/ | ||
public EmptyResponse changePassword(ChangePasswordRequest request, RequestOptions options) throws IOException { | ||
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::changePassword, options, | ||
EmptyResponse::fromXContent, emptySet()); | ||
} | ||
|
||
/** | ||
* Change the password of a user in the native realm asynchronously. | ||
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. maybe say |
||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-change-password.html"> | ||
* the docs</a> for more. | ||
* | ||
* @param request the request with the user's new password | ||
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized | ||
* @param listener the listener to be notified upon request completion | ||
*/ | ||
public void changePasswordAsync(ChangePasswordRequest request, RequestOptions options, ActionListener<EmptyResponse> | ||
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. can you wrap the type of the parameter too? Things line up better and are easier to read IMO |
||
listener) { | ||
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::changePassword, options, | ||
EmptyResponse::fromXContent, listener, emptySet()); | ||
|
||
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: remove empty line |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,7 +19,9 @@ | |
|
||
package org.elasticsearch.client; | ||
|
||
import org.apache.http.client.methods.HttpPost; | ||
import org.apache.http.client.methods.HttpPut; | ||
import org.elasticsearch.client.security.ChangePasswordRequest; | ||
import org.elasticsearch.client.security.PutUserRequest; | ||
|
||
import java.io.IOException; | ||
|
@@ -31,6 +33,20 @@ public final class SecurityRequestConverters { | |
|
||
private SecurityRequestConverters() {} | ||
|
||
static Request changePassword(ChangePasswordRequest changePasswordRequest) throws IOException { | ||
String endpoint = new RequestConverters.EndpointBuilder() | ||
.addPathPartAsIs("_xpack/security/user") | ||
.addPathPart(changePasswordRequest.getUsername()) | ||
.addPathPartAsIs("_password") | ||
.build(); | ||
Request request = new Request(HttpPost.METHOD_NAME, endpoint); | ||
request.setEntity(createEntity(changePasswordRequest, REQUEST_BODY_CONTENT_TYPE)); | ||
RequestConverters.Params params = new RequestConverters.Params(request); | ||
params.withRefreshPolicy(changePasswordRequest.getRefreshPolicy()); | ||
return request; | ||
} | ||
|
||
|
||
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: extra line 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. +1 to this nit |
||
static Request putUser(PutUserRequest putUserRequest) throws IOException { | ||
String endpoint = new RequestConverters.EndpointBuilder() | ||
.addPathPartAsIs("_xpack/security/user") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
/* | ||
* Licensed to Elasticsearch under one or more contributor | ||
* license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright | ||
* ownership. Elasticsearch licenses this file to you under | ||
* the Apache License, Version 2.0 (the "License"); you may | ||
* not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package org.elasticsearch.client.security; | ||
|
||
import org.elasticsearch.client.Validatable; | ||
import org.elasticsearch.client.ValidationException; | ||
import org.elasticsearch.common.CharArrays; | ||
import org.elasticsearch.common.xcontent.ToXContentObject; | ||
import org.elasticsearch.common.xcontent.XContentBuilder; | ||
|
||
import java.io.Closeable; | ||
import java.io.IOException; | ||
import java.util.Arrays; | ||
import java.util.Objects; | ||
import java.util.Optional; | ||
|
||
public final class ChangePasswordRequest implements Validatable, Closeable, ToXContentObject { | ||
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. can you add class level javadocs? 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 think it would be easier for clients if we don't implement Let me know what you think! In any case we should be pedantic and mention the "ownership" in the javadocs. 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 see no issue with your line of argumentation @albertzaharovits. As this applies to other request objects, I'd summon @jaymode to weigh-in, so that we can decide and aim for consistency. 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 think @albertzaharovits's line of thinking makes sense and let's not implement closeable. In the PutUserRequest I did not claim ownership but the array is cleared by the close method. I'll open a PR to resolve the issue there. |
||
|
||
private final String username; | ||
private final char[] password; | ||
private final RefreshPolicy refreshPolicy; | ||
|
||
public ChangePasswordRequest(String username, char[] password, RefreshPolicy refreshPolicy) { | ||
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 would add a javadoc to this constructor:
I am being pedantic because this is the interface to clients. 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. +1 to add javadocs; I need to go back and do so for my HLRC APIs. |
||
this.username = username; | ||
this.password = Objects.requireNonNull(password, "password is required"); | ||
this.refreshPolicy = refreshPolicy == null ? RefreshPolicy.getDefault() : refreshPolicy; | ||
} | ||
|
||
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: remove extra new line |
||
|
||
public String getUsername() { | ||
return username; | ||
} | ||
|
||
public char[] getPassword() { | ||
return password; | ||
} | ||
|
||
public RefreshPolicy getRefreshPolicy() { | ||
return refreshPolicy; | ||
} | ||
|
||
@Override | ||
public boolean equals(Object o){ | ||
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. do we need the equals/hashcode implementations? |
||
if (this == o) return true; | ||
if (o == null || getClass() != o.getClass()) return false; | ||
ChangePasswordRequest that = (ChangePasswordRequest) o; | ||
return Objects.equals(username, that.username) && | ||
Arrays.equals(password, that.password) && | ||
refreshPolicy == that.refreshPolicy; | ||
} | ||
|
||
@Override | ||
public int hashCode() { | ||
int result = Objects.hash(username, refreshPolicy); | ||
result = 31 * result + Arrays.hashCode(password); | ||
return result; | ||
} | ||
@Override | ||
public void close() throws IOException { | ||
if (password != null) { | ||
Arrays.fill(password, '\u0000'); | ||
} | ||
} | ||
|
||
@Override | ||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { | ||
builder.startObject(); | ||
if (null != username){ | ||
builder.field("username", username); | ||
} | ||
if (password != null) { | ||
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. password should never be null based on the constructor check? |
||
byte[] charBytes = CharArrays.toUtf8Bytes(password); | ||
builder.field("password").utf8Value(charBytes, 0, charBytes.length); | ||
} | ||
return builder.endObject(); | ||
} | ||
jaymode marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@Override | ||
public Optional<ValidationException> validate() { | ||
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. no need to implement/override this method if no validation is required |
||
return Optional.empty(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* Licensed to Elasticsearch under one or more contributor | ||
* license agreements. See the NOTICE file distributed with | ||
* this work for additional information regarding copyright | ||
* ownership. Elasticsearch licenses this file to you under | ||
* the Apache License, Version 2.0 (the "License"); you may | ||
* not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, | ||
* software distributed under the License is distributed on an | ||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | ||
* KIND, either express or implied. See the License for the | ||
* specific language governing permissions and limitations | ||
* under the License. | ||
*/ | ||
|
||
package org.elasticsearch.client.security; | ||
|
||
import org.elasticsearch.common.xcontent.ObjectParser; | ||
import org.elasticsearch.common.xcontent.XContentParser; | ||
|
||
import java.io.IOException; | ||
|
||
/** | ||
* Response for a request which simply returns an empty object. | ||
*/ | ||
public final class EmptyResponse { | ||
|
||
private static final ObjectParser<EmptyResponse, Void> PARSER = new ObjectParser<>("empty_response", false, EmptyResponse::new); | ||
|
||
public static EmptyResponse fromXContent(XContentParser parser) throws IOException { | ||
return PARSER.parse(parser, null); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,6 +24,8 @@ | |
import org.elasticsearch.client.ESRestHighLevelClientTestCase; | ||
import org.elasticsearch.client.RequestOptions; | ||
import org.elasticsearch.client.RestHighLevelClient; | ||
import org.elasticsearch.client.security.ChangePasswordRequest; | ||
import org.elasticsearch.client.security.EmptyResponse; | ||
import org.elasticsearch.client.security.PutUserRequest; | ||
import org.elasticsearch.client.security.PutUserResponse; | ||
import org.elasticsearch.client.security.RefreshPolicy; | ||
|
@@ -81,4 +83,50 @@ public void onFailure(Exception e) { | |
assertTrue(latch.await(30L, TimeUnit.SECONDS)); | ||
} | ||
} | ||
|
||
public void testChangePassword() throws Exception { | ||
RestHighLevelClient client = highLevelClient(); | ||
char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}; | ||
char[] newPassword = new char[]{'n', 'e', 'w', 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}; | ||
PutUserRequest putUserRequest = | ||
new PutUserRequest("change_password_user", password, Collections.singletonList("superuser"), null, null, true, null, | ||
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. can you change the wrapping so it is only on two lines instead of three? |
||
RefreshPolicy.NONE); | ||
PutUserResponse putUserResponse = client.security().putUser(putUserRequest, RequestOptions.DEFAULT); | ||
assertTrue(putUserResponse.isCreated()); | ||
{ | ||
//tag::change-password-execute | ||
ChangePasswordRequest request = new ChangePasswordRequest("change_password_user", newPassword, RefreshPolicy.NONE); | ||
EmptyResponse response = client.security().changePassword(request, RequestOptions.DEFAULT); | ||
//end::change-password-execute | ||
|
||
assertNotNull(response); | ||
|
||
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: extra line |
||
} | ||
{ | ||
//tag::change-password-execute-listener | ||
ChangePasswordRequest request = new ChangePasswordRequest("change_password_user", password, RefreshPolicy.NONE); | ||
ActionListener<EmptyResponse> listener = new ActionListener<EmptyResponse>() { | ||
@Override | ||
public void onResponse(EmptyResponse emptyResponse) { | ||
// <1> | ||
} | ||
|
||
@Override | ||
public void onFailure(Exception e) { | ||
// <2> | ||
} | ||
}; | ||
//end::change-password-execute-listener | ||
|
||
// Replace the empty listener by a blocking listener in test | ||
final CountDownLatch latch = new CountDownLatch(1); | ||
listener = new LatchedActionListener<>(listener, latch); | ||
|
||
//tag::x-pack-put-user-execute-async | ||
client.security().changePasswordAsync(request, RequestOptions.DEFAULT, listener); // <1> | ||
//end::x-pack-put-user-execute-async | ||
|
||
assertTrue(latch.await(30L, TimeUnit.SECONDS)); | ||
} | ||
} | ||
} |
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.
maybe say
of a native realm or built-in user