mirror of
https://github.com/honeymoose/OpenSearch.git
synced 2025-02-05 20:48:22 +00:00
HLRC: Add security Create Token API (#34791)
This adds the Create Token API (POST /_xpack/security/oauth2/token) to the High Level Rest Client. Relates: #29827
This commit is contained in:
parent
bb5b59004e
commit
9c27b407f0
@ -88,6 +88,7 @@ integTestCluster {
|
||||
systemProperty 'es.scripting.update.ctx_in_params', 'false'
|
||||
setting 'xpack.license.self_generated.type', 'trial'
|
||||
setting 'xpack.security.enabled', 'true'
|
||||
setting 'xpack.security.authc.token.enabled', 'true'
|
||||
// Truststore settings are not used since TLS is not enabled. Included for testing the get certificates API
|
||||
setting 'xpack.ssl.certificate_authorities', 'testnode.crt'
|
||||
setting 'xpack.security.transport.ssl.truststore.path', 'testnode.jks'
|
||||
|
@ -20,12 +20,15 @@
|
||||
package org.elasticsearch.client;
|
||||
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.client.security.ChangePasswordRequest;
|
||||
import org.elasticsearch.client.security.ClearRolesCacheRequest;
|
||||
import org.elasticsearch.client.security.ClearRolesCacheResponse;
|
||||
import org.elasticsearch.client.security.CreateTokenRequest;
|
||||
import org.elasticsearch.client.security.CreateTokenResponse;
|
||||
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
|
||||
import org.elasticsearch.client.security.DeleteRoleMappingResponse;
|
||||
import org.elasticsearch.client.security.DeleteRoleRequest;
|
||||
import org.elasticsearch.client.security.DeleteRoleResponse;
|
||||
import org.elasticsearch.client.security.PutRoleMappingRequest;
|
||||
import org.elasticsearch.client.security.PutRoleMappingResponse;
|
||||
import org.elasticsearch.client.security.DisableUserRequest;
|
||||
import org.elasticsearch.client.security.EmptyResponse;
|
||||
import org.elasticsearch.client.security.EnableUserRequest;
|
||||
@ -33,11 +36,10 @@ import org.elasticsearch.client.security.GetRoleMappingsRequest;
|
||||
import org.elasticsearch.client.security.GetRoleMappingsResponse;
|
||||
import org.elasticsearch.client.security.GetSslCertificatesRequest;
|
||||
import org.elasticsearch.client.security.GetSslCertificatesResponse;
|
||||
import org.elasticsearch.client.security.PutRoleMappingRequest;
|
||||
import org.elasticsearch.client.security.PutRoleMappingResponse;
|
||||
import org.elasticsearch.client.security.PutUserRequest;
|
||||
import org.elasticsearch.client.security.PutUserResponse;
|
||||
import org.elasticsearch.client.security.ChangePasswordRequest;
|
||||
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
|
||||
import org.elasticsearch.client.security.DeleteRoleMappingResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@ -350,4 +352,32 @@ public final class SecurityClient {
|
||||
DeleteRoleResponse::fromXContent, listener, singleton(404));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an OAuth2 token.
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-token.html">
|
||||
* the docs</a> for more.
|
||||
*
|
||||
* @param request the request for the token
|
||||
* @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized
|
||||
* @return the response from the create token call
|
||||
* @throws IOException in case there is a problem sending the request or parsing back the response
|
||||
*/
|
||||
public CreateTokenResponse createToken(CreateTokenRequest request, RequestOptions options) throws IOException {
|
||||
return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::createToken, options,
|
||||
CreateTokenResponse::fromXContent, emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously creates an OAuth2 token.
|
||||
* See <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-get-token.html">
|
||||
* the docs</a> for more.
|
||||
*
|
||||
* @param request the request for the token
|
||||
* @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 createTokenAsync(CreateTokenRequest request, RequestOptions options, ActionListener<CreateTokenResponse> listener) {
|
||||
restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::createToken, options,
|
||||
CreateTokenResponse::fromXContent, listener, emptySet());
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.elasticsearch.client.security.ClearRolesCacheRequest;
|
||||
import org.elasticsearch.client.security.CreateTokenRequest;
|
||||
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
|
||||
import org.elasticsearch.client.security.DeleteRoleRequest;
|
||||
import org.elasticsearch.client.security.PutRoleMappingRequest;
|
||||
@ -140,4 +141,10 @@ final class SecurityRequestConverters {
|
||||
params.withRefreshPolicy(deleteRoleRequest.getRefreshPolicy());
|
||||
return request;
|
||||
}
|
||||
|
||||
static Request createToken(CreateTokenRequest createTokenRequest) throws IOException {
|
||||
Request request = new Request(HttpPost.METHOD_NAME, "/_xpack/security/oauth2/token");
|
||||
request.setEntity(createEntity(createTokenRequest, REQUEST_BODY_CONTENT_TYPE));
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,152 @@
|
||||
/*
|
||||
* 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.common.CharArrays;
|
||||
import org.elasticsearch.common.Nullable;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Request to create a new OAuth2 token from the Elasticsearch cluster.
|
||||
*/
|
||||
public final class CreateTokenRequest implements Validatable, ToXContentObject {
|
||||
|
||||
private final String grantType;
|
||||
private final String scope;
|
||||
private final String username;
|
||||
private final char[] password;
|
||||
private final String refreshToken;
|
||||
|
||||
/**
|
||||
* General purpose constructor. This constructor is typically not useful, and one of the following factory methods should be used
|
||||
* instead:
|
||||
* <ul>
|
||||
* <li>{@link #passwordGrant(String, char[])}</li>
|
||||
* <li>{@link #refreshTokenGrant(String)}</li>
|
||||
* <li>{@link #clientCredentialsGrant()}</li>
|
||||
* </ul>
|
||||
*/
|
||||
public CreateTokenRequest(String grantType, @Nullable String scope, @Nullable String username, @Nullable char[] password,
|
||||
@Nullable String refreshToken) {
|
||||
if (Strings.isNullOrEmpty(grantType)) {
|
||||
throw new IllegalArgumentException("grant_type is required");
|
||||
}
|
||||
this.grantType = grantType;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.scope = scope;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public static CreateTokenRequest passwordGrant(String username, char[] password) {
|
||||
if (Strings.isNullOrEmpty(username)) {
|
||||
throw new IllegalArgumentException("username is required");
|
||||
}
|
||||
if (password == null || password.length == 0) {
|
||||
throw new IllegalArgumentException("password is required");
|
||||
}
|
||||
return new CreateTokenRequest("password", null, username, password, null);
|
||||
}
|
||||
|
||||
public static CreateTokenRequest refreshTokenGrant(String refreshToken) {
|
||||
if (Strings.isNullOrEmpty(refreshToken)) {
|
||||
throw new IllegalArgumentException("refresh_token is required");
|
||||
}
|
||||
return new CreateTokenRequest("refresh_token", null, null, null, refreshToken);
|
||||
}
|
||||
|
||||
public static CreateTokenRequest clientCredentialsGrant() {
|
||||
return new CreateTokenRequest("client_credentials", null, null, null, null);
|
||||
}
|
||||
|
||||
public String getGrantType() {
|
||||
return grantType;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
public char[] getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
builder.startObject()
|
||||
.field("grant_type", grantType);
|
||||
if (scope != null) {
|
||||
builder.field("scope", scope);
|
||||
}
|
||||
if (username != null) {
|
||||
builder.field("username", username);
|
||||
}
|
||||
if (password != null) {
|
||||
byte[] passwordBytes = CharArrays.toUtf8Bytes(password);
|
||||
try {
|
||||
builder.field("password").utf8Value(passwordBytes, 0, passwordBytes.length);
|
||||
} finally {
|
||||
Arrays.fill(passwordBytes, (byte) 0);
|
||||
}
|
||||
}
|
||||
if (refreshToken != null) {
|
||||
builder.field("refresh_token", refreshToken);
|
||||
}
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final CreateTokenRequest that = (CreateTokenRequest) o;
|
||||
return Objects.equals(grantType, that.grantType) &&
|
||||
Objects.equals(scope, that.scope) &&
|
||||
Objects.equals(username, that.username) &&
|
||||
Arrays.equals(password, that.password) &&
|
||||
Objects.equals(refreshToken, that.refreshToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Objects.hash(grantType, scope, username, refreshToken);
|
||||
result = 31 * result + Arrays.hashCode(password);
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
/*
|
||||
* 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.ParseField;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
|
||||
|
||||
/**
|
||||
* Response when creating a new OAuth2 token in the Elasticsearch cluster. Contains an access token, the token's expiry, and an optional
|
||||
* refresh token.
|
||||
*/
|
||||
public final class CreateTokenResponse {
|
||||
|
||||
private final String accessToken;
|
||||
private final String type;
|
||||
private final TimeValue expiresIn;
|
||||
private final String scope;
|
||||
private final String refreshToken;
|
||||
|
||||
public CreateTokenResponse(String accessToken, String type, TimeValue expiresIn, String scope, String refreshToken) {
|
||||
this.accessToken = accessToken;
|
||||
this.type = type;
|
||||
this.expiresIn = expiresIn;
|
||||
this.scope = scope;
|
||||
this.refreshToken = refreshToken;
|
||||
}
|
||||
|
||||
public String getAccessToken() {
|
||||
return accessToken;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public TimeValue getExpiresIn() {
|
||||
return expiresIn;
|
||||
}
|
||||
|
||||
public String getScope() {
|
||||
return scope;
|
||||
}
|
||||
|
||||
public String getRefreshToken() {
|
||||
return refreshToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final CreateTokenResponse that = (CreateTokenResponse) o;
|
||||
return Objects.equals(accessToken, that.accessToken) &&
|
||||
Objects.equals(type, that.type) &&
|
||||
Objects.equals(expiresIn, that.expiresIn) &&
|
||||
Objects.equals(scope, that.scope) &&
|
||||
Objects.equals(refreshToken, that.refreshToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(accessToken, type, expiresIn, scope, refreshToken);
|
||||
}
|
||||
|
||||
private static final ConstructingObjectParser<CreateTokenResponse, Void> PARSER = new ConstructingObjectParser<>(
|
||||
"create_token_response", true, args -> new CreateTokenResponse(
|
||||
(String) args[0], (String) args[1], TimeValue.timeValueSeconds((Long) args[2]), (String) args[3], (String) args[4]));
|
||||
|
||||
static {
|
||||
PARSER.declareString(constructorArg(), new ParseField("access_token"));
|
||||
PARSER.declareString(constructorArg(), new ParseField("type"));
|
||||
PARSER.declareLong(constructorArg(), new ParseField("expires_in"));
|
||||
PARSER.declareStringOrNull(optionalConstructorArg(), new ParseField("scope"));
|
||||
PARSER.declareStringOrNull(optionalConstructorArg(), new ParseField("refresh_token"));
|
||||
}
|
||||
|
||||
public static CreateTokenResponse fromXContent(XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, null);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.client.methods.HttpDelete;
|
||||
import org.apache.http.client.methods.HttpPost;
|
||||
import org.apache.http.client.methods.HttpPut;
|
||||
import org.elasticsearch.client.security.CreateTokenRequest;
|
||||
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
|
||||
import org.elasticsearch.client.security.DeleteRoleRequest;
|
||||
import org.elasticsearch.client.security.DisableUserRequest;
|
||||
@ -211,4 +212,34 @@ public class SecurityRequestConvertersTests extends ESTestCase {
|
||||
assertEquals(expectedParams, request.getParameters());
|
||||
assertNull(request.getEntity());
|
||||
}
|
||||
|
||||
public void testCreateTokenWithPasswordGrant() throws Exception {
|
||||
final String username = randomAlphaOfLengthBetween(1, 12);
|
||||
final String password = randomAlphaOfLengthBetween(8, 12);
|
||||
CreateTokenRequest createTokenRequest = CreateTokenRequest.passwordGrant(username, password.toCharArray());
|
||||
Request request = SecurityRequestConverters.createToken(createTokenRequest);
|
||||
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
|
||||
assertEquals("/_xpack/security/oauth2/token", request.getEndpoint());
|
||||
assertEquals(0, request.getParameters().size());
|
||||
assertToXContentBody(createTokenRequest, request.getEntity());
|
||||
}
|
||||
|
||||
public void testCreateTokenWithRefreshTokenGrant() throws Exception {
|
||||
final String refreshToken = randomAlphaOfLengthBetween(8, 24);
|
||||
CreateTokenRequest createTokenRequest = CreateTokenRequest.refreshTokenGrant(refreshToken);
|
||||
Request request = SecurityRequestConverters.createToken(createTokenRequest);
|
||||
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
|
||||
assertEquals("/_xpack/security/oauth2/token", request.getEndpoint());
|
||||
assertEquals(0, request.getParameters().size());
|
||||
assertToXContentBody(createTokenRequest, request.getEntity());
|
||||
}
|
||||
|
||||
public void testCreateTokenWithClientCredentialsGrant() throws Exception {
|
||||
CreateTokenRequest createTokenRequest = CreateTokenRequest.clientCredentialsGrant();
|
||||
Request request = SecurityRequestConverters.createToken(createTokenRequest);
|
||||
assertEquals(HttpPost.METHOD_NAME, request.getMethod());
|
||||
assertEquals("/_xpack/security/oauth2/token", request.getEndpoint());
|
||||
assertEquals(0, request.getParameters().size());
|
||||
assertToXContentBody(createTokenRequest, request.getEntity());
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.nio.entity.NStringEntity;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.LatchedActionListener;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.client.ESRestHighLevelClientTestCase;
|
||||
import org.elasticsearch.client.Request;
|
||||
import org.elasticsearch.client.RequestOptions;
|
||||
@ -31,6 +32,8 @@ import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.client.security.ChangePasswordRequest;
|
||||
import org.elasticsearch.client.security.ClearRolesCacheRequest;
|
||||
import org.elasticsearch.client.security.ClearRolesCacheResponse;
|
||||
import org.elasticsearch.client.security.CreateTokenRequest;
|
||||
import org.elasticsearch.client.security.CreateTokenResponse;
|
||||
import org.elasticsearch.client.security.DeleteRoleMappingRequest;
|
||||
import org.elasticsearch.client.security.DeleteRoleMappingResponse;
|
||||
import org.elasticsearch.client.security.DeleteRoleRequest;
|
||||
@ -668,4 +671,79 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
||||
client().performRequest(addRoleRequest);
|
||||
}
|
||||
|
||||
public void testCreateToken() throws Exception {
|
||||
RestHighLevelClient client = highLevelClient();
|
||||
|
||||
{
|
||||
// Setup user
|
||||
PutUserRequest putUserRequest = new PutUserRequest("token_user", "password".toCharArray(),
|
||||
Collections.singletonList("kibana_user"), null, null, true, null, RefreshPolicy.IMMEDIATE);
|
||||
PutUserResponse putUserResponse = client.security().putUser(putUserRequest, RequestOptions.DEFAULT);
|
||||
assertTrue(putUserResponse.isCreated());
|
||||
}
|
||||
{
|
||||
// tag::create-token-password-request
|
||||
final char[] password = new char[]{'p', 'a', 's', 's', 'w', 'o', 'r', 'd'};
|
||||
CreateTokenRequest createTokenRequest = CreateTokenRequest.passwordGrant("token_user", password);
|
||||
// end::create-token-password-request
|
||||
|
||||
// tag::create-token-execute
|
||||
CreateTokenResponse createTokenResponse = client.security().createToken(createTokenRequest, RequestOptions.DEFAULT);
|
||||
// end::create-token-execute
|
||||
|
||||
// tag::create-token-response
|
||||
String accessToken = createTokenResponse.getAccessToken(); // <1>
|
||||
String refreshToken = createTokenResponse.getRefreshToken(); // <2>
|
||||
// end::create-token-response
|
||||
assertNotNull(accessToken);
|
||||
assertNotNull(refreshToken);
|
||||
assertNotNull(createTokenResponse.getExpiresIn());
|
||||
|
||||
// tag::create-token-refresh-request
|
||||
createTokenRequest = CreateTokenRequest.refreshTokenGrant(refreshToken);
|
||||
// end::create-token-refresh-request
|
||||
|
||||
CreateTokenResponse refreshResponse = client.security().createToken(createTokenRequest, RequestOptions.DEFAULT);
|
||||
assertNotNull(refreshResponse.getAccessToken());
|
||||
assertNotNull(refreshResponse.getRefreshToken());
|
||||
}
|
||||
|
||||
{
|
||||
// tag::create-token-client-credentials-request
|
||||
CreateTokenRequest createTokenRequest = CreateTokenRequest.clientCredentialsGrant();
|
||||
// end::create-token-client-credentials-request
|
||||
|
||||
ActionListener<CreateTokenResponse> listener;
|
||||
//tag::create-token-execute-listener
|
||||
listener = new ActionListener<CreateTokenResponse>() {
|
||||
@Override
|
||||
public void onResponse(CreateTokenResponse createTokenResponse) {
|
||||
// <1>
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Exception e) {
|
||||
// <2>
|
||||
}
|
||||
};
|
||||
//end::create-token-execute-listener
|
||||
|
||||
// Avoid unused variable warning
|
||||
assertNotNull(listener);
|
||||
|
||||
// Replace the empty listener by a blocking listener in test
|
||||
final PlainActionFuture<CreateTokenResponse> future = new PlainActionFuture<>();
|
||||
listener = future;
|
||||
|
||||
//tag::create-token-execute-async
|
||||
client.security().createTokenAsync(createTokenRequest, RequestOptions.DEFAULT, listener); // <1>
|
||||
//end::create-token-execute-async
|
||||
|
||||
assertNotNull(future.get(30, TimeUnit.SECONDS));
|
||||
assertNotNull(future.get().getAccessToken());
|
||||
// "client-credentials" grants aren't refreshable
|
||||
assertNull(future.get().getRefreshToken());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.Strings;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
||||
public class CreateTokenRequestTests extends ESTestCase {
|
||||
|
||||
public void testCreateTokenFromPassword() {
|
||||
final CreateTokenRequest request = CreateTokenRequest.passwordGrant("jsmith", "top secret password".toCharArray());
|
||||
assertThat(request.getGrantType(), equalTo("password"));
|
||||
assertThat(request.getUsername(), equalTo("jsmith"));
|
||||
assertThat(new String(request.getPassword()), equalTo("top secret password"));
|
||||
assertThat(request.getScope(), nullValue());
|
||||
assertThat(request.getRefreshToken(), nullValue());
|
||||
assertThat(Strings.toString(request), equalTo("{" +
|
||||
"\"grant_type\":\"password\"," +
|
||||
"\"username\":\"jsmith\"," +
|
||||
"\"password\":\"top secret password\"" +
|
||||
"}"
|
||||
));
|
||||
}
|
||||
|
||||
public void testCreateTokenFromRefreshToken() {
|
||||
final CreateTokenRequest request = CreateTokenRequest.refreshTokenGrant("9a7f41cf-9918-4d1f-bfaa-ad3f8f9f02b9");
|
||||
assertThat(request.getGrantType(), equalTo("refresh_token"));
|
||||
assertThat(request.getRefreshToken(), equalTo("9a7f41cf-9918-4d1f-bfaa-ad3f8f9f02b9"));
|
||||
assertThat(request.getScope(), nullValue());
|
||||
assertThat(request.getUsername(), nullValue());
|
||||
assertThat(request.getPassword(), nullValue());
|
||||
assertThat(Strings.toString(request), equalTo("{" +
|
||||
"\"grant_type\":\"refresh_token\"," +
|
||||
"\"refresh_token\":\"9a7f41cf-9918-4d1f-bfaa-ad3f8f9f02b9\"" +
|
||||
"}"
|
||||
));
|
||||
}
|
||||
|
||||
public void testCreateTokenFromClientCredentials() {
|
||||
final CreateTokenRequest request = CreateTokenRequest.clientCredentialsGrant();
|
||||
assertThat(request.getGrantType(), equalTo("client_credentials"));
|
||||
assertThat(request.getScope(), nullValue());
|
||||
assertThat(request.getUsername(), nullValue());
|
||||
assertThat(request.getPassword(), nullValue());
|
||||
assertThat(request.getRefreshToken(), nullValue());
|
||||
assertThat(Strings.toString(request), equalTo("{\"grant_type\":\"client_credentials\"}"));
|
||||
}
|
||||
|
||||
public void testEqualsAndHashCode() {
|
||||
final String grantType = randomAlphaOfLength(8);
|
||||
final String scope = randomBoolean() ? null : randomAlphaOfLength(6);
|
||||
final String username = randomBoolean() ? null : randomAlphaOfLengthBetween(4, 10);
|
||||
final char[] password = randomBoolean() ? null : randomAlphaOfLengthBetween(8, 12).toCharArray();
|
||||
final String refreshToken = randomBoolean() ? null : randomAlphaOfLengthBetween(12, 24);
|
||||
final CreateTokenRequest request = new CreateTokenRequest(grantType, scope, username, password, refreshToken);
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(request,
|
||||
r -> new CreateTokenRequest(r.getGrantType(), r.getScope(), r.getUsername(), r.getPassword(), r.getRefreshToken()),
|
||||
this::mutate);
|
||||
}
|
||||
|
||||
private CreateTokenRequest mutate(CreateTokenRequest req) {
|
||||
switch (randomIntBetween(1, 5)) {
|
||||
case 1:
|
||||
return new CreateTokenRequest("g", req.getScope(), req.getUsername(), req.getPassword(), req.getRefreshToken());
|
||||
case 2:
|
||||
return new CreateTokenRequest(req.getGrantType(), "s", req.getUsername(), req.getPassword(), req.getRefreshToken());
|
||||
case 3:
|
||||
return new CreateTokenRequest(req.getGrantType(), req.getScope(), "u", req.getPassword(), req.getRefreshToken());
|
||||
case 4:
|
||||
final char[] password = {'p'};
|
||||
return new CreateTokenRequest(req.getGrantType(), req.getScope(), req.getUsername(), password, req.getRefreshToken());
|
||||
case 5:
|
||||
return new CreateTokenRequest(req.getGrantType(), req.getScope(), req.getUsername(), req.getPassword(), "r");
|
||||
}
|
||||
throw new IllegalStateException("Bad random number");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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.bytes.BytesReference;
|
||||
import org.elasticsearch.common.unit.TimeValue;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class CreateTokenResponseTests extends ESTestCase {
|
||||
|
||||
public void testFromXContent() throws IOException {
|
||||
final String accessToken = randomAlphaOfLengthBetween(12, 24);
|
||||
final TimeValue expiresIn = TimeValue.timeValueSeconds(randomIntBetween(30, 10_000));
|
||||
final String refreshToken = randomBoolean() ? null : randomAlphaOfLengthBetween(12, 24);
|
||||
final String scope = randomBoolean() ? null : randomAlphaOfLength(4);
|
||||
final String type = randomAlphaOfLength(6);
|
||||
|
||||
final XContentType xContentType = randomFrom(XContentType.values());
|
||||
final XContentBuilder builder = XContentFactory.contentBuilder(xContentType);
|
||||
builder.startObject()
|
||||
.field("access_token", accessToken)
|
||||
.field("type", type)
|
||||
.field("expires_in", expiresIn.seconds());
|
||||
if (refreshToken != null || randomBoolean()) {
|
||||
builder.field("refresh_token", refreshToken);
|
||||
}
|
||||
if (scope != null || randomBoolean()) {
|
||||
builder.field("scope", scope);
|
||||
}
|
||||
builder.endObject();
|
||||
BytesReference xContent = BytesReference.bytes(builder);
|
||||
|
||||
final CreateTokenResponse response = CreateTokenResponse.fromXContent(createParser(xContentType.xContent(), xContent));
|
||||
assertThat(response.getAccessToken(), equalTo(accessToken));
|
||||
assertThat(response.getRefreshToken(), equalTo(refreshToken));
|
||||
assertThat(response.getScope(), equalTo(scope));
|
||||
assertThat(response.getType(), equalTo(type));
|
||||
assertThat(response.getExpiresIn(), equalTo(expiresIn));
|
||||
}
|
||||
}
|
85
docs/java-rest/high-level/security/create-token.asciidoc
Normal file
85
docs/java-rest/high-level/security/create-token.asciidoc
Normal file
@ -0,0 +1,85 @@
|
||||
[[java-rest-high-security-create-token]]
|
||||
=== Create Token API
|
||||
|
||||
[[java-rest-high-security-create-token-request]]
|
||||
==== Request
|
||||
The `CreateTokenRequest` supports three different OAuth2 _grant types_:
|
||||
|
||||
===== Password Grants
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SecurityDocumentationIT.java[create-token-password-request]
|
||||
--------------------------------------------------
|
||||
|
||||
===== Refresh Token Grants
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SecurityDocumentationIT.java[create-token-refresh-request]
|
||||
--------------------------------------------------
|
||||
|
||||
===== Client Credential Grants
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SecurityDocumentationIT.java[create-token-client-credentials-request]
|
||||
--------------------------------------------------
|
||||
|
||||
[[java-rest-high-security-create-token-execution]]
|
||||
==== Execution
|
||||
|
||||
Creating a OAuth2 security token can be performed by passing the appropriate request to the
|
||||
`security().createToken()` method:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SecurityDocumentationIT.java[create-token-execute]
|
||||
--------------------------------------------------
|
||||
|
||||
[[java-rest-high-security-create-token-response]]
|
||||
==== Response
|
||||
|
||||
The returned `CreateTokenResponse` contains the following properties:
|
||||
|
||||
`accessToken`:: This is the newly created access token.
|
||||
It can be used to authenticate to the Elasticsearch cluster.
|
||||
`type`:: The type of the token, this is always `"Bearer"`.
|
||||
`expiresIn`:: The length of time until the token will expire.
|
||||
The token will be considered invalid after that time.
|
||||
`scope`:: The scope of the token. May be `null`.
|
||||
`refreshToken`:: A secondary "refresh" token that may be used to extend
|
||||
the life of an access token. May be `null`.
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SecurityDocumentationIT.java[create-token-response]
|
||||
--------------------------------------------------
|
||||
<1> The `accessToken` can be used to authentication to Elasticsearch.
|
||||
<2> The `refreshToken` can be used in to create a new `CreateTokenRequest` with a `refresh_token` grant.
|
||||
|
||||
[[java-rest-high-security-create-token-async]]
|
||||
==== Asynchronous Execution
|
||||
|
||||
This request can be executed asynchronously using the `security().createTokenAsync()`
|
||||
method:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SecurityDocumentationIT.java[create-token-execute-async]
|
||||
--------------------------------------------------
|
||||
<1> The `CreateTokenRequest` to execute and the `ActionListener` to use when
|
||||
the execution completes
|
||||
|
||||
The asynchronous method does not block and returns immediately. Once the request
|
||||
has completed the `ActionListener` is called back using the `onResponse` method
|
||||
if the execution successfully completed or using the `onFailure` method if
|
||||
it failed.
|
||||
|
||||
A typical listener for a `CreateTokenResponse` looks like:
|
||||
|
||||
["source","java",subs="attributes,callouts,macros"]
|
||||
--------------------------------------------------
|
||||
include-tagged::{doc-tests}/SecurityDocumentationIT.java[create-token-execute-listener]
|
||||
--------------------------------------------------
|
||||
<1> Called when the execution is successfully completed. The response is
|
||||
provided as an argument
|
||||
<2> Called in case of failure. The raised exception is provided as an argument
|
@ -329,6 +329,7 @@ The Java High Level REST Client supports the following Security APIs:
|
||||
* <<java-rest-high-security-put-role-mapping>>
|
||||
* <<java-rest-high-security-get-role-mappings>>
|
||||
* <<java-rest-high-security-delete-role-mapping>>
|
||||
* <<java-rest-high-security-create-token>>
|
||||
|
||||
include::security/put-user.asciidoc[]
|
||||
include::security/enable-user.asciidoc[]
|
||||
@ -340,6 +341,7 @@ include::security/get-certificates.asciidoc[]
|
||||
include::security/put-role-mapping.asciidoc[]
|
||||
include::security/get-role-mappings.asciidoc[]
|
||||
include::security/delete-role-mapping.asciidoc[]
|
||||
include::security/create-token.asciidoc[]
|
||||
|
||||
== Watcher APIs
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user