diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java index 811b9c9053ee8..d1a74030992ac 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityClient.java @@ -56,6 +56,8 @@ import org.elasticsearch.client.security.PutPrivilegesResponse; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.PutRoleMappingResponse; +import org.elasticsearch.client.security.PutRoleRequest; +import org.elasticsearch.client.security.PutRoleResponse; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.PutUserResponse; @@ -461,7 +463,7 @@ public void getRolesAsync(GetRolesRequest request, RequestOptions options, Actio * * @param request the request with the roles to get * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized - * @return the response from the delete role call + * @return the response from the get roles call * @throws IOException in case there is a problem sending the request or parsing back the response */ public GetRolesResponse getRoles(final GetRolesRequest request, final RequestOptions options) throws IOException { @@ -469,6 +471,35 @@ public GetRolesResponse getRoles(final GetRolesRequest request, final RequestOpt GetRolesResponse::fromXContent, emptySet()); } + /** + * Asynchronously creates or updates a role in the native roles store. + * See + * the docs for more. + * + * @param request the request containing the role to create or update + * @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 putRoleAsync(PutRoleRequest request, RequestOptions options, ActionListener listener) { + restHighLevelClient.performRequestAsyncAndParseEntity(request, SecurityRequestConverters::putRole, options, + PutRoleResponse::fromXContent, listener, emptySet()); + } + + /** + * Create or update a role in the native roles store. + * See + * the docs for more. + * + * @param request the request containing the role to create or update + * @param options the request options (e.g. headers), use {@link RequestOptions#DEFAULT} if nothing needs to be customized + * @return the response from the put role call + * @throws IOException in case there is a problem sending the request or parsing back the response + */ + public PutRoleResponse putRole(final PutRoleRequest request, final RequestOptions options) throws IOException { + return restHighLevelClient.performRequestAndParseEntity(request, SecurityRequestConverters::putRole, options, + PutRoleResponse::fromXContent, emptySet()); + } + /** * Asynchronously delete a role mapping. * See diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java index d893c20c3b2fd..a37966d0ff59a 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/SecurityRequestConverters.java @@ -40,6 +40,7 @@ import org.elasticsearch.client.security.InvalidateTokenRequest; import org.elasticsearch.client.security.PutPrivilegesRequest; import org.elasticsearch.client.security.PutRoleMappingRequest; +import org.elasticsearch.client.security.PutRoleRequest; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.SetUserEnabledRequest; import org.elasticsearch.common.Strings; @@ -233,4 +234,16 @@ static Request deletePrivileges(DeletePrivilegesRequest deletePrivilegeRequest) params.withRefreshPolicy(deletePrivilegeRequest.getRefreshPolicy()); return request; } + + static Request putRole(final PutRoleRequest putRoleRequest) throws IOException { + final String endpoint = new RequestConverters.EndpointBuilder() + .addPathPartAsIs("_xpack/security/role") + .addPathPart(putRoleRequest.getRole().getName()) + .build(); + final Request request = new Request(HttpPut.METHOD_NAME, endpoint); + request.setEntity(createEntity(putRoleRequest, REQUEST_BODY_CONTENT_TYPE)); + final RequestConverters.Params params = new RequestConverters.Params(request); + params.withRefreshPolicy(putRoleRequest.getRefreshPolicy()); + return request; + } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java index 91b7527c3235e..1a937ea5cfeee 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/GetRolesResponse.java @@ -20,13 +20,16 @@ package org.elasticsearch.client.security; import org.elasticsearch.client.security.user.privileges.Role; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParserUtils; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -36,24 +39,37 @@ public final class GetRolesResponse { private final List roles; + private final Map> transientMetadataMap; - public GetRolesResponse(List roles) { + GetRolesResponse(List roles, Map> transientMetadataMap) { this.roles = Collections.unmodifiableList(roles); + this.transientMetadataMap = Collections.unmodifiableMap(transientMetadataMap); } public List getRoles() { return roles; } + public Map> getTransientMetadataMap() { + return transientMetadataMap; + } + + public Map getTransientMetadata(String roleName) { + return transientMetadataMap.get(roleName); + } + public static GetRolesResponse fromXContent(XContentParser parser) throws IOException { XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser::getTokenLocation); final List roles = new ArrayList<>(); + final Map> transientMetadata = new HashMap<>(); XContentParser.Token token; while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { XContentParserUtils.ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); - roles.add(Role.PARSER.parse(parser, parser.currentName())); + final Tuple> roleAndTransientMetadata = Role.PARSER.parse(parser, parser.currentName()); + roles.add(roleAndTransientMetadata.v1()); + transientMetadata.put(roleAndTransientMetadata.v1().getName(), roleAndTransientMetadata.v2()); } - return new GetRolesResponse(roles); + return new GetRolesResponse(roles, transientMetadata); } @Override @@ -61,11 +77,12 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; GetRolesResponse response = (GetRolesResponse) o; - return Objects.equals(roles, response.roles); + return Objects.equals(roles, response.roles) + && Objects.equals(transientMetadataMap, response.transientMetadataMap); } @Override public int hashCode() { - return Objects.hash(roles); + return Objects.hash(roles, transientMetadataMap); } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleRequest.java new file mode 100644 index 0000000000000..da9106744bac4 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleRequest.java @@ -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.client.Validatable; +import org.elasticsearch.client.security.user.privileges.Role; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; + +import java.io.IOException; +import java.util.Objects; + +/** + * Request object to create or update a role. + */ +public final class PutRoleRequest implements Validatable, ToXContentObject { + + private final Role role; + private final RefreshPolicy refreshPolicy; + + public PutRoleRequest(Role role, @Nullable final RefreshPolicy refreshPolicy) { + this.role = Objects.requireNonNull(role); + this.refreshPolicy = (refreshPolicy == null) ? RefreshPolicy.getDefault() : refreshPolicy; + } + + public Role getRole() { + return role; + } + + public RefreshPolicy getRefreshPolicy() { + return refreshPolicy; + } + + @Override + public int hashCode() { + return Objects.hash(role, refreshPolicy); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final PutRoleRequest other = (PutRoleRequest) obj; + + return (refreshPolicy == other.getRefreshPolicy()) && + Objects.equals(role, other.role); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (role.getApplicationResourcePrivileges() != null) { + builder.field(Role.APPLICATIONS.getPreferredName(), role.getApplicationResourcePrivileges()); + } + if (role.getClusterPrivileges() != null) { + builder.field(Role.CLUSTER.getPreferredName(), role.getClusterPrivileges()); + } + if (role.getGlobalApplicationPrivileges() != null) { + builder.field(Role.GLOBAL.getPreferredName(), role.getGlobalApplicationPrivileges()); + } + if (role.getIndicesPrivileges() != null) { + builder.field(Role.INDICES.getPreferredName(), role.getIndicesPrivileges()); + } + if (role.getMetadata() != null) { + builder.field(Role.METADATA.getPreferredName(), role.getMetadata()); + } + if (role.getRunAsPrivilege() != null) { + builder.field(Role.RUN_AS.getPreferredName(), role.getRunAsPrivilege()); + } + return builder.endObject(); + } + +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleResponse.java new file mode 100644 index 0000000000000..b42782b7d9430 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/PutRoleResponse.java @@ -0,0 +1,82 @@ +/* + * 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.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentParser.Token; + +import java.io.IOException; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureFieldName; + +/** + * Response when adding a role to the native roles store. Returns a + * single boolean field for whether the role was created (true) or updated (false). + */ +public final class PutRoleResponse { + + private final boolean created; + + public PutRoleResponse(boolean created) { + this.created = created; + } + + public boolean isCreated() { + return created; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PutRoleResponse that = (PutRoleResponse) o; + return created == that.created; + } + + @Override + public int hashCode() { + return Objects.hash(created); + } + + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("put_role_response", + true, args -> new PutRoleResponse((boolean) args[0])); + + static { + PARSER.declareBoolean(constructorArg(), new ParseField("created")); + } + + public static PutRoleResponse fromXContent(XContentParser parser) throws IOException { + if (parser.currentToken() == null) { + parser.nextToken(); + } + // parse extraneous wrapper + ensureExpectedToken(Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation); + ensureFieldName(parser, parser.nextToken(), "role"); + parser.nextToken(); + final PutRoleResponse roleResponse = PARSER.parse(parser, null); + ensureExpectedToken(Token.END_OBJECT, parser.nextToken(), parser::getTokenLocation); + return roleResponse; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/GlobalPrivileges.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/GlobalPrivileges.java index 891980765427e..c1bd5550eca1a 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/GlobalPrivileges.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/GlobalPrivileges.java @@ -26,7 +26,6 @@ import org.elasticsearch.common.xcontent.XContentParser; import java.io.IOException; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -49,7 +48,7 @@ public final class GlobalPrivileges implements ToXContentObject { // When categories change, adapting this field should suffice. Categories are NOT // opaque "named_objects", we wish to maintain control over these namespaces - static final List CATEGORIES = Collections.unmodifiableList(Arrays.asList("application")); + public static final List CATEGORIES = Collections.singletonList("application"); @SuppressWarnings("unchecked") static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("global_category_privileges", @@ -134,4 +133,4 @@ public int hashCode() { return Objects.hash(privileges); } -} \ No newline at end of file +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java index 393b8613f25e7..188b8b4ea14dc 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/IndicesPrivileges.java @@ -217,12 +217,12 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws builder.startObject(); builder.field(NAMES.getPreferredName(), indices); builder.field(PRIVILEGES.getPreferredName(), privileges); - if (isUsingFieldLevelSecurity()) { + if (grantedFields != null || deniedFields != null) { builder.startObject(FIELD_PERMISSIONS.getPreferredName()); if (grantedFields != null) { builder.field(GRANT_FIELDS.getPreferredName(), grantedFields); } - if (hasDeniedFields()) { + if (deniedFields != null) { builder.field(EXCEPT_FIELDS.getPreferredName(), deniedFields); } builder.endObject(); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java index e332971a512fd..27af02fdf50db 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/security/user/privileges/Role.java @@ -22,6 +22,7 @@ import org.elasticsearch.common.Nullable; import org.elasticsearch.common.ParseField; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.collect.Tuple; import org.elasticsearch.common.xcontent.ConstructingObjectParser; import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.ObjectParser.ValueType; @@ -34,7 +35,6 @@ import java.util.Objects; import java.util.Set; -import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; /** @@ -51,8 +51,8 @@ public final class Role { public static final ParseField TRANSIENT_METADATA = new ParseField("transient_metadata"); @SuppressWarnings("unchecked") - public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("role_descriptor", false, - (constructorObjects, roleName) -> { + public static final ConstructingObjectParser>, String> PARSER = + new ConstructingObjectParser<>("role_descriptor", false, (constructorObjects, roleName) -> { // Don't ignore unknown fields. It is dangerous if the object we parse is also // part of a request that we build later on, and the fields that we now ignore // will end up being implicitly set to null in that request. @@ -65,8 +65,10 @@ public final class Role { final Collection runAsPrivilege = (Collection) constructorObjects[i++]; final Map metadata = (Map) constructorObjects[i++]; final Map transientMetadata = (Map) constructorObjects[i]; - return new Role(roleName, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges, - runAsPrivilege, metadata, transientMetadata); + return new Tuple<>( + new Role(roleName, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges, + runAsPrivilege, metadata), + transientMetadata != null ? Collections.unmodifiableMap(transientMetadata) : Collections.emptyMap()); }); static { @@ -77,8 +79,8 @@ public final class Role { PARSER.declareFieldArray(optionalConstructorArg(), (parser,c)->ApplicationResourcePrivileges.PARSER.parse(parser,null), APPLICATIONS, ValueType.OBJECT_ARRAY); PARSER.declareStringArray(optionalConstructorArg(), RUN_AS); - PARSER.declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA); - PARSER.declareObject(constructorArg(), (parser, c) -> parser.map(), TRANSIENT_METADATA); + PARSER.declareObject(optionalConstructorArg(), (parser, c) -> parser.map(), METADATA); + PARSER.declareObject(optionalConstructorArg(), (parser, c) -> parser.map(), TRANSIENT_METADATA); } private final String name; @@ -88,14 +90,12 @@ public final class Role { private final Set applicationResourcePrivileges; private final Set runAsPrivilege; private final Map metadata; - private final Map transientMetadata; private Role(String name, @Nullable Collection clusterPrivileges, @Nullable GlobalPrivileges globalApplicationPrivileges, @Nullable Collection indicesPrivileges, @Nullable Collection applicationResourcePrivileges, - @Nullable Collection runAsPrivilege, @Nullable Map metadata, - @Nullable Map transientMetadata) { + @Nullable Collection runAsPrivilege, @Nullable Map metadata) { if (Strings.hasText(name) == false){ throw new IllegalArgumentException("role name must be provided"); } else { @@ -114,7 +114,6 @@ private Role(String name, @Nullable Collection clusterPrivileges, // no run as privileges are granted unless otherwise specified this.runAsPrivilege = Collections.unmodifiableSet(runAsPrivilege != null ? new HashSet<>(runAsPrivilege) : Collections.emptySet()); this.metadata = metadata != null ? Collections.unmodifiableMap(metadata) : Collections.emptyMap(); - this.transientMetadata = transientMetadata != null ? Collections.unmodifiableMap(transientMetadata) : Collections.emptyMap(); } public String getName() { @@ -156,14 +155,13 @@ public boolean equals(Object o) { && indicesPrivileges.equals(that.indicesPrivileges) && applicationResourcePrivileges.equals(that.applicationResourcePrivileges) && runAsPrivilege.equals(that.runAsPrivilege) - && metadata.equals(that.metadata) - && transientMetadata.equals(that.transientMetadata); + && metadata.equals(that.metadata); } @Override public int hashCode() { return Objects.hash(name, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges, - runAsPrivilege, metadata, transientMetadata); + runAsPrivilege, metadata); } @Override @@ -200,16 +198,11 @@ public String toString() { sb.append(metadata.toString()); sb.append("], "); } - if (false == transientMetadata.isEmpty()) { - sb.append("TransientMetadata=["); - sb.append(transientMetadata.toString()); - sb.append("] "); - } sb.append("}"); return sb.toString(); } - public static Role fromXContent(XContentParser parser, String name) { + public static Tuple> fromXContent(XContentParser parser, String name) { return PARSER.apply(parser, name); } @@ -226,7 +219,6 @@ public static final class Builder { private @Nullable Collection applicationResourcePrivileges = null; private @Nullable Collection runAsPrivilege = null; private @Nullable Map metadata = null; - private @Nullable Map transientMetadata = null; private Builder() { } @@ -294,15 +286,9 @@ public Builder metadata(Map metadata) { return this; } - public Builder transientMetadata(Map transientMetadata) { - this.transientMetadata = - Objects.requireNonNull(transientMetadata, "Transient metadata cannot be null. Pass an empty map instead."); - return this; - } - public Role build() { return new Role(name, clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges, - runAsPrivilege, metadata, transientMetadata); + runAsPrivilege, metadata); } } @@ -329,6 +315,9 @@ public static class ClusterPrivilegeName { public static final String MANAGE_PIPELINE = "manage_pipeline"; public static final String MANAGE_CCR = "manage_ccr"; public static final String READ_CCR = "read_ccr"; + public static final String[] ALL_ARRAY = new String[] { NONE, ALL, MONITOR, MONITOR_ML, MONITOR_WATCHER, MONITOR_ROLLUP, MANAGE, + MANAGE_ML, MANAGE_WATCHER, MANAGE_ROLLUP, MANAGE_INDEX_TEMPLATES, MANAGE_INGEST_PIPELINES, TRANSPORT_CLIENT, + MANAGE_SECURITY, MANAGE_SAML, MANAGE_TOKEN, MANAGE_PIPELINE, MANAGE_CCR, READ_CCR }; } /** @@ -349,6 +338,8 @@ public static class IndexPrivilegeName { public static final String CREATE_INDEX = "create_index"; public static final String VIEW_INDEX_METADATA = "view_index_metadata"; public static final String MANAGE_FOLLOW_INDEX = "manage_follow_index"; + public static final String[] ALL_ARRAY = new String[] { NONE, ALL, READ, READ_CROSS, CREATE, INDEX, DELETE, WRITE, MONITOR, MANAGE, + DELETE_INDEX, CREATE_INDEX, VIEW_INDEX_METADATA, MANAGE_FOLLOW_INDEX }; } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java index 27b1d31e6d7d5..466cc5b0135dc 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityIT.java @@ -22,22 +22,36 @@ import org.apache.http.client.methods.HttpDelete; import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.client.security.AuthenticateResponse; +import org.elasticsearch.client.security.DeleteRoleRequest; +import org.elasticsearch.client.security.DeleteRoleResponse; import org.elasticsearch.client.security.DeleteUserRequest; import org.elasticsearch.client.security.DeleteUserResponse; +import org.elasticsearch.client.security.GetRolesRequest; +import org.elasticsearch.client.security.GetRolesResponse; +import org.elasticsearch.client.security.PutRoleRequest; +import org.elasticsearch.client.security.PutRoleResponse; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.PutUserResponse; import org.elasticsearch.client.security.RefreshPolicy; import org.elasticsearch.client.security.user.User; +import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivileges; +import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivilegesTests; +import org.elasticsearch.client.security.user.privileges.GlobalPrivilegesTests; +import org.elasticsearch.client.security.user.privileges.IndicesPrivileges; +import org.elasticsearch.client.security.user.privileges.IndicesPrivilegesTests; +import org.elasticsearch.client.security.user.privileges.Role; import org.elasticsearch.common.CharArrays; import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.contains; public class SecurityIT extends ESRestHighLevelClientTestCase { @@ -94,6 +108,31 @@ public void testAuthenticate() throws Exception { assertThat(deleteUserResponse2.isAcknowledged(), is(false)); } + public void testPutRole() throws Exception { + final SecurityClient securityClient = highLevelClient().security(); + // create random role + final Role role = randomRole(randomAlphaOfLength(4)); + final PutRoleRequest putRoleRequest = new PutRoleRequest(role, RefreshPolicy.IMMEDIATE); + + final PutRoleResponse createRoleResponse = execute(putRoleRequest, securityClient::putRole, securityClient::putRoleAsync); + // assert role created + assertThat(createRoleResponse.isCreated(), is(true)); + + final GetRolesRequest getRoleRequest = new GetRolesRequest(role.getName()); + final GetRolesResponse getRoleResponse = securityClient.getRoles(getRoleRequest, RequestOptions.DEFAULT); + // assert role is equal + assertThat(getRoleResponse.getRoles(), contains(role)); + + final PutRoleResponse updateRoleResponse = execute(putRoleRequest, securityClient::putRole, securityClient::putRoleAsync); + // assert role updated + assertThat(updateRoleResponse.isCreated(), is(false)); + + final DeleteRoleRequest deleteRoleRequest = new DeleteRoleRequest(role.getName()); + final DeleteRoleResponse deleteRoleResponse = securityClient.deleteRole(deleteRoleRequest, RequestOptions.DEFAULT); + // assert role deleted + assertThat(deleteRoleResponse.isFound(), is(true)); + } + private static User randomUser() { final String username = randomAlphaOfLengthBetween(1, 4); return randomUser(username); @@ -118,6 +157,28 @@ private static User randomUser(String username) { return new User(username, roles, metadata, fullName, email); } + private static Role randomRole(String roleName) { + final Role.Builder roleBuilder = Role.builder() + .name(roleName) + .clusterPrivileges(randomSubsetOf(randomInt(3), Role.ClusterPrivilegeName.ALL_ARRAY)) + .indicesPrivileges( + randomArray(3, IndicesPrivileges[]::new, () -> IndicesPrivilegesTests.createNewRandom(randomAlphaOfLength(3)))) + .applicationResourcePrivileges(randomArray(3, ApplicationResourcePrivileges[]::new, + () -> ApplicationResourcePrivilegesTests.createNewRandom(randomAlphaOfLength(3).toLowerCase(Locale.ROOT)))) + .runAsPrivilege(randomArray(3, String[]::new, () -> randomAlphaOfLength(3))); + if (randomBoolean()) { + roleBuilder.globalApplicationPrivileges(GlobalPrivilegesTests.buildRandomManageApplicationPrivilege()); + } + if (randomBoolean()) { + final Map metadata = new HashMap<>(); + for (int i = 0; i < randomInt(3); i++) { + metadata.put(randomAlphaOfLength(3), randomAlphaOfLength(3)); + } + roleBuilder.metadata(metadata); + } + return roleBuilder.build(); + } + private static PutUserRequest randomPutUserRequest(boolean enabled) { final User user = randomUser(); return randomPutUserRequest(user, enabled); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java index f5015c1203dea..489b52468eac6 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SecurityRequestConvertersTests.java @@ -36,13 +36,17 @@ import org.elasticsearch.client.security.GetRolesRequest; import org.elasticsearch.client.security.PutPrivilegesRequest; import org.elasticsearch.client.security.PutRoleMappingRequest; +import org.elasticsearch.client.security.PutRoleRequest; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.RefreshPolicy; import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression; import org.elasticsearch.client.security.support.expressiondsl.expressions.AnyRoleMapperExpression; import org.elasticsearch.client.security.support.expressiondsl.fields.FieldRoleMapperExpression; import org.elasticsearch.client.security.user.User; +import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivileges; import org.elasticsearch.client.security.user.privileges.ApplicationPrivilege; +import org.elasticsearch.client.security.user.privileges.IndicesPrivileges; +import org.elasticsearch.client.security.user.privileges.Role; import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.test.ESTestCase; @@ -357,4 +361,37 @@ public void testDeletePrivileges() { assertEquals(expectedParams, request.getParameters()); assertNull(request.getEntity()); } + + public void testPutRole() throws IOException { + final String roleName = randomAlphaOfLengthBetween(4, 7); + final List clusterPrivileges = randomSubsetOf(3, Role.ClusterPrivilegeName.ALL_ARRAY); + final Map metadata = Collections.singletonMap(randomAlphaOfLengthBetween(4, 7), randomAlphaOfLengthBetween(4, 7)); + final String[] runAsPrivilege = randomArray(3, String[]::new, () -> randomAlphaOfLength(5)); + final List applicationPrivilegeNames = Arrays.asList(randomArray(1, 3, String[]::new, () -> randomAlphaOfLength(5))); + final List applicationResouceNames = Arrays.asList(randomArray(1, 3, String[]::new, () -> randomAlphaOfLength(5))); + final ApplicationResourcePrivileges applicationResourcePrivilege = new ApplicationResourcePrivileges( + randomAlphaOfLengthBetween(4, 7), applicationPrivilegeNames, applicationResouceNames); + final List indicesName = Arrays.asList(randomArray(1, 3, String[]::new, () -> randomAlphaOfLength(5))); + final List indicesPrivilegeName = Arrays.asList(randomArray(1, 3, String[]::new, () -> randomAlphaOfLength(5))); + final List indicesPrivilegeGrantedName = Arrays.asList(randomArray(3, String[]::new, () -> randomAlphaOfLength(5))); + final List indicesPrivilegeDeniedName = Arrays.asList(randomArray(3, String[]::new, () -> randomAlphaOfLength(5))); + final String indicesPrivilegeQuery = randomAlphaOfLengthBetween(0, 7); + final IndicesPrivileges indicesPrivilege = IndicesPrivileges.builder().indices(indicesName).privileges(indicesPrivilegeName) + .grantedFields(indicesPrivilegeGrantedName).deniedFields(indicesPrivilegeDeniedName).query(indicesPrivilegeQuery).build(); + final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values()); + final Map expectedParams; + if (refreshPolicy != RefreshPolicy.NONE) { + expectedParams = Collections.singletonMap("refresh", refreshPolicy.getValue()); + } else { + expectedParams = Collections.emptyMap(); + } + final Role role = Role.builder().name(roleName).clusterPrivileges(clusterPrivileges).indicesPrivileges(indicesPrivilege) + .applicationResourcePrivileges(applicationResourcePrivilege).runAsPrivilege(runAsPrivilege).metadata(metadata).build(); + final PutRoleRequest putRoleRequest = new PutRoleRequest(role, refreshPolicy); + final Request request = SecurityRequestConverters.putRole(putRoleRequest); + assertEquals(HttpPut.METHOD_NAME, request.getMethod()); + assertEquals("/_xpack/security/role/" + roleName, request.getEndpoint()); + assertEquals(expectedParams, request.getParameters()); + assertToXContentBody(putRoleRequest, request.getEntity()); + } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java index 480f7abb131e1..27d8e755e973d 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/SecurityDocumentationIT.java @@ -19,15 +19,11 @@ package org.elasticsearch.client.documentation; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ContentType; -import org.apache.http.nio.entity.NStringEntity; import org.elasticsearch.ElasticsearchStatusException; 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; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.security.AuthenticateResponse; @@ -65,6 +61,8 @@ import org.elasticsearch.client.security.PutPrivilegesResponse; import org.elasticsearch.client.security.PutRoleMappingRequest; import org.elasticsearch.client.security.PutRoleMappingResponse; +import org.elasticsearch.client.security.PutRoleRequest; +import org.elasticsearch.client.security.PutRoleResponse; import org.elasticsearch.client.security.PutUserRequest; import org.elasticsearch.client.security.PutUserResponse; import org.elasticsearch.client.security.RefreshPolicy; @@ -76,9 +74,7 @@ import org.elasticsearch.client.security.user.privileges.Role; import org.elasticsearch.client.security.user.privileges.ApplicationPrivilege; import org.elasticsearch.client.security.user.privileges.IndicesPrivileges; -import org.elasticsearch.common.Strings; import org.elasticsearch.common.util.set.Sets; -import org.elasticsearch.common.xcontent.XContentBuilder; import org.hamcrest.Matchers; import javax.crypto.SecretKeyFactory; @@ -97,7 +93,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsInAnyOrder; @@ -1024,18 +1019,67 @@ public void onFailure(Exception e) { } } - // TODO: move all calls to high-level REST client once APIs for adding new role exist - private void addRole(String roleName) throws IOException { - Request addRoleRequest = new Request(HttpPost.METHOD_NAME, "/_xpack/security/role/" + roleName); - try (XContentBuilder builder = jsonBuilder()) { - builder.startObject(); - { - builder.array("cluster", "all"); - } - builder.endObject(); - addRoleRequest.setEntity(new NStringEntity(Strings.toString(builder), ContentType.APPLICATION_JSON)); + public void testPutRole() throws Exception { + RestHighLevelClient client = highLevelClient(); + + { + // tag::put-role-execute + final Role role = Role.builder() + .name("testPutRole") + .clusterPrivileges(randomSubsetOf(1, Role.ClusterPrivilegeName.ALL_ARRAY)) + .build(); + final PutRoleRequest request = new PutRoleRequest(role, RefreshPolicy.NONE); + final PutRoleResponse response = client.security().putRole(request, RequestOptions.DEFAULT); + // end::put-role-execute + // tag::put-role-response + boolean isCreated = response.isCreated(); // <1> + // end::put-role-response + assertTrue(isCreated); + } + + { + final Role role = Role.builder() + .name("testPutRole") + .clusterPrivileges(randomSubsetOf(1, Role.ClusterPrivilegeName.ALL_ARRAY)) + .build(); + final PutRoleRequest request = new PutRoleRequest(role, RefreshPolicy.NONE); + // tag::put-role-execute-listener + ActionListener listener = new ActionListener() { + @Override + public void onResponse(PutRoleResponse response) { + // <1> + } + + @Override + public void onFailure(Exception e) { + // <2> + } + }; + // end::put-role-execute-listener + + // Avoid unused variable warning + assertNotNull(listener); + + // Replace the empty listener by a blocking listener in test + final PlainActionFuture future = new PlainActionFuture<>(); + listener = future; + + // tag::put-role-execute-async + client.security().putRoleAsync(request, RequestOptions.DEFAULT, listener); // <1> + // end::put-role-execute-async + + assertNotNull(future.get(30, TimeUnit.SECONDS)); + assertThat(future.get().isCreated(), is(false)); // false because it has already been created by the sync variant } - client().performRequest(addRoleRequest); + } + + private void addRole(String roleName) throws IOException { + final Role role = Role.builder() + .name(roleName) + .clusterPrivileges("all") + .build(); + final PutRoleRequest request = new PutRoleRequest(role, RefreshPolicy.IMMEDIATE); + highLevelClient().security().putRole(request, RequestOptions.DEFAULT); } public void testCreateToken() throws Exception { diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java index 41de52a8cef75..224b4d59d250d 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/GetRolesResponseTests.java @@ -73,6 +73,7 @@ public void usedDeprecatedField(String usedName, String replacedWith) { } }, json))); assertThat(response.getRoles().size(), equalTo(1)); + assertThat(response.getTransientMetadataMap().size(), equalTo(1)); final Role role = response.getRoles().get(0); assertThat(role.getName(), equalTo("my_admin_role")); assertThat(role.getClusterPrivileges().size(), equalTo(1)); @@ -86,19 +87,20 @@ public void usedDeprecatedField(String usedName, String replacedWith) { expectedMetadata.put("version", 1); final Map expectedTransientMetadata = new HashMap<>(); expectedTransientMetadata.put("enabled", true); + assertThat(response.getTransientMetadataMap().get(role.getName()), equalTo(expectedTransientMetadata)); final Role expectedRole = Role.builder() .name("my_admin_role") .clusterPrivileges("all") .indicesPrivileges(expectedIndicesPrivileges) .runAsPrivilege("other_user") .metadata(expectedMetadata) - .transientMetadata(expectedTransientMetadata) .build(); assertThat(role, equalTo(expectedRole)); } public void testEqualsHashCode() { final List roles = new ArrayList<>(); + final Map> transientMetadataMap = new HashMap<>(); IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder() .indices("index1", "index2") .privileges("write", "monitor", "delete") @@ -107,17 +109,17 @@ public void testEqualsHashCode() { .build(); Map metadata = new HashMap<>(); metadata.put("key", "value"); - Map transientMetadata = new HashMap<>(); - transientMetadata.put("transient_key", "transient_value"); final Role role = Role.builder() .name("role_name") .clusterPrivileges("monitor", "manage", "manage_saml") .indicesPrivileges(indicesPrivileges) .runAsPrivilege("run_as_user") .metadata(metadata) - .transientMetadata(transientMetadata) .build(); roles.add(role); + Map transientMetadata = new HashMap<>(); + transientMetadata.put("transient_key", "transient_value"); + transientMetadataMap.put(role.getName(), transientMetadata); IndicesPrivileges indicesPrivileges2 = new IndicesPrivileges.Builder() .indices("other_index1", "other_index2") .privileges("write", "monitor", "delete") @@ -126,31 +128,31 @@ public void testEqualsHashCode() { .build(); Map metadata2 = new HashMap<>(); metadata.put("other_key", "other_value"); - Map transientMetadata2 = new HashMap<>(); - transientMetadata2.put("other_transient_key", "other_transient_value"); final Role role2 = Role.builder() .name("role2_name") .clusterPrivileges("monitor", "manage", "manage_saml") .indicesPrivileges(indicesPrivileges2) .runAsPrivilege("other_run_as_user") .metadata(metadata2) - .transientMetadata(transientMetadata2) .build(); roles.add(role2); - final GetRolesResponse getRolesResponse = new GetRolesResponse(roles); - assertNotNull(getRolesResponse); + Map transientMetadata2 = new HashMap<>(); + transientMetadata2.put("other_transient_key", "other_transient_value"); + transientMetadataMap.put(role2.getName(), transientMetadata); + final GetRolesResponse getRolesResponse = new GetRolesResponse(roles, transientMetadataMap); EqualsHashCodeTestUtils.checkEqualsAndHashCode(getRolesResponse, (original) -> { - return new GetRolesResponse(original.getRoles()); + return new GetRolesResponse(original.getRoles(), original.getTransientMetadataMap()); }); EqualsHashCodeTestUtils.checkEqualsAndHashCode(getRolesResponse, (original) -> { - return new GetRolesResponse(original.getRoles()); + return new GetRolesResponse(original.getRoles(), original.getTransientMetadataMap()); }, GetRolesResponseTests::mutateTestItem); } private static GetRolesResponse mutateTestItem(GetRolesResponse original) { + final List roles = new ArrayList<>(); + final Map> transientMetadataMap = new HashMap<>(); if (randomBoolean()) { - final List roles = new ArrayList<>(); IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder() .indices("index1", "index2") .privileges("write", "monitor", "delete") @@ -159,18 +161,18 @@ private static GetRolesResponse mutateTestItem(GetRolesResponse original) { .build(); Map metadata = new HashMap(); metadata.put("key", "value"); - Map transientMetadata = new HashMap<>(); - transientMetadata.put("transient_key", "transient_value"); final Role role = Role.builder() .name("role_name") .clusterPrivileges("monitor", "manage", "manage_saml") .indicesPrivileges(indicesPrivileges) .runAsPrivilege("run_as_user") .metadata(metadata) - .transientMetadata(transientMetadata) .build(); roles.add(role); - return new GetRolesResponse(roles); + Map transientMetadata = new HashMap<>(); + transientMetadata.put("transient_key", "transient_value"); + transientMetadataMap.put(role.getName(), transientMetadata); + return new GetRolesResponse(roles, transientMetadataMap); } else { IndicesPrivileges indicesPrivileges = new IndicesPrivileges.Builder() .indices("index1_changed", "index2") @@ -180,20 +182,20 @@ private static GetRolesResponse mutateTestItem(GetRolesResponse original) { .build(); Map metadata = new HashMap(); metadata.put("key", "value"); - Map transientMetadata = new HashMap<>(); - transientMetadata.put("transient_key", "transient_value"); final Role role = Role.builder() .name("role_name") .clusterPrivileges("monitor", "manage", "manage_saml") .indicesPrivileges(indicesPrivileges) .runAsPrivilege("run_as_user") .metadata(metadata) - .transientMetadata(transientMetadata) .build(); List newRoles = original.getRoles().stream().collect(Collectors.toList()); newRoles.remove(0); newRoles.add(role); - return new GetRolesResponse(newRoles); + Map transientMetadata = new HashMap<>(); + transientMetadata.put("transient_key", "transient_value"); + transientMetadataMap.put(role.getName(), transientMetadata); + return new GetRolesResponse(newRoles, transientMetadataMap); } } } diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutRoleRequestTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutRoleRequestTests.java new file mode 100644 index 0000000000000..32860a807ae67 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/PutRoleRequestTests.java @@ -0,0 +1,87 @@ +/* + * 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.security.user.privileges.ApplicationResourcePrivileges; +import org.elasticsearch.client.security.user.privileges.ApplicationResourcePrivilegesTests; +import org.elasticsearch.client.security.user.privileges.GlobalOperationPrivilege; +import org.elasticsearch.client.security.user.privileges.GlobalPrivileges; +import org.elasticsearch.client.security.user.privileges.GlobalPrivilegesTests; +import org.elasticsearch.client.security.user.privileges.IndicesPrivileges; +import org.elasticsearch.client.security.user.privileges.IndicesPrivilegesTests; +import org.elasticsearch.client.security.user.privileges.Role; +import org.elasticsearch.common.collect.Tuple; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.empty; + +public class PutRoleRequestTests extends AbstractXContentTestCase { + + private static final String roleName = "testRoleName"; + + @Override + protected PutRoleRequest createTestInstance() { + final Role role = randomRole(roleName); + return new PutRoleRequest(role, null); + } + + @Override + protected PutRoleRequest doParseInstance(XContentParser parser) throws IOException { + final Tuple> roleAndTransientMetadata = Role.fromXContent(parser, roleName); + assertThat(roleAndTransientMetadata.v2().entrySet(), is(empty())); + return new PutRoleRequest(roleAndTransientMetadata.v1(), null); + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } + + private static Role randomRole(String roleName) { + final Role.Builder roleBuilder = Role.builder().name(roleName) + .clusterPrivileges(randomSubsetOf(randomInt(3), Role.ClusterPrivilegeName.ALL_ARRAY)) + .indicesPrivileges( + randomArray(3, IndicesPrivileges[]::new, () -> IndicesPrivilegesTests.createNewRandom(randomAlphaOfLength(3)))) + .applicationResourcePrivileges(randomArray(3, ApplicationResourcePrivileges[]::new, + () -> ApplicationResourcePrivilegesTests.createNewRandom(randomAlphaOfLength(3).toLowerCase(Locale.ROOT)))) + .runAsPrivilege(randomArray(3, String[]::new, () -> randomAlphaOfLength(3))); + if (randomBoolean()) { + roleBuilder.globalApplicationPrivileges(new GlobalPrivileges(Arrays.asList( + randomArray(1, 3, GlobalOperationPrivilege[]::new, () -> GlobalPrivilegesTests.buildRandomGlobalScopedPrivilege())))); + } + if (randomBoolean()) { + final Map metadata = new HashMap<>(); + for (int i = 0; i < randomInt(3); i++) { + metadata.put(randomAlphaOfLength(3), randomAlphaOfLength(3)); + } + roleBuilder.metadata(metadata); + } + return roleBuilder.build(); + } + +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivilegesTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivilegesTests.java index 9575363a40963..29845441a2c05 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivilegesTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/ApplicationResourcePrivilegesTests.java @@ -31,13 +31,17 @@ public class ApplicationResourcePrivilegesTests extends AbstractXContentTestCase { - @Override - protected ApplicationResourcePrivileges createTestInstance() { - return new ApplicationResourcePrivileges(randomAlphaOfLengthBetween(1, 8), + public static ApplicationResourcePrivileges createNewRandom(String name) { + return new ApplicationResourcePrivileges(name, Arrays.asList(randomArray(1, 8, size -> new String[size], () -> randomAlphaOfLengthBetween(1, 8))), Arrays.asList(randomArray(1, 8, size -> new String[size], () -> randomAlphaOfLengthBetween(1, 8)))); } + @Override + protected ApplicationResourcePrivileges createTestInstance() { + return createNewRandom(randomAlphaOfLengthBetween(1, 8)); + } + @Override protected ApplicationResourcePrivileges doParseInstance(XContentParser parser) throws IOException { return ApplicationResourcePrivileges.fromXContent(parser); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/GlobalPrivilegesTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/GlobalPrivilegesTests.java index a9d2702970962..927372c839e7c 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/GlobalPrivilegesTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/GlobalPrivilegesTests.java @@ -36,6 +36,25 @@ public class GlobalPrivilegesTests extends AbstractXContentTestCase privilege = new HashMap<>(); + privilege.put("applications", Arrays.asList(generateRandomStringArray(4, 4, false))); + final GlobalOperationPrivilege priv = new GlobalOperationPrivilege("application", "manage", privilege); + return new GlobalPrivileges(Arrays.asList(priv)); + } + + public static GlobalOperationPrivilege buildRandomGlobalScopedPrivilege() { + final Map privilege = new HashMap<>(); + for (int i = 0; i < randomIntBetween(1, 4); i++) { + if (randomBoolean()) { + privilege.put(randomAlphaOfLength(2) + idCounter++, randomAlphaOfLengthBetween(0, 4)); + } else { + privilege.put(randomAlphaOfLength(2) + idCounter++, Arrays.asList(generateRandomStringArray(4, 4, false))); + } + } + return new GlobalOperationPrivilege(randomFrom(GlobalPrivileges.CATEGORIES), randomAlphaOfLength(2) + idCounter++, privilege); + } + @Override protected GlobalPrivileges createTestInstance() { final List privilegeList = Arrays @@ -85,14 +104,6 @@ public void testSameScopeGlobalOperationPrivilege() { assertThat(e.getMessage(), is("Different privileges for the same category and operation are not permitted")); } - private static GlobalOperationPrivilege buildRandomGlobalScopedPrivilege() { - final Map privilege = new HashMap<>(); - for (int i = 0; i < randomIntBetween(1, 4); i++) { - privilege.put(randomAlphaOfLength(2) + idCounter++, randomAlphaOfLengthBetween(1, 4)); - } - return new GlobalOperationPrivilege("application", randomAlphaOfLength(2) + idCounter++, privilege); - } - public void testEqualsHashCode() { final List privilegeList = Arrays .asList(randomArray(1, 4, size -> new GlobalOperationPrivilege[size], () -> buildRandomGlobalScopedPrivilege())); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/IndicesPrivilegesTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/IndicesPrivilegesTests.java new file mode 100644 index 0000000000000..41442d2a83687 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/security/user/privileges/IndicesPrivilegesTests.java @@ -0,0 +1,61 @@ +/* + * 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.user.privileges; + +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractXContentTestCase; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +public class IndicesPrivilegesTests extends AbstractXContentTestCase { + + public static IndicesPrivileges createNewRandom(String query) { + final IndicesPrivileges.Builder indicesPrivilegesBuilder = IndicesPrivileges.builder() + .indices(generateRandomStringArray(4, 4, false, false)) + .privileges(randomSubsetOf(randomIntBetween(1, 4), Role.IndexPrivilegeName.ALL_ARRAY)) + .query(query); + if (randomBoolean()) { + final List fields = Arrays.asList(generateRandomStringArray(4, 4, false)); + indicesPrivilegesBuilder.grantedFields(fields); + if (randomBoolean()) { + indicesPrivilegesBuilder.deniedFields(randomSubsetOf(fields)); + } + } + return indicesPrivilegesBuilder.build(); + } + + @Override + protected IndicesPrivileges createTestInstance() { + return createNewRandom( + randomBoolean() ? null : "{ " + randomAlphaOfLengthBetween(1, 4) + " : " + randomAlphaOfLengthBetween(1, 4) + " }"); + } + + @Override + protected IndicesPrivileges doParseInstance(XContentParser parser) throws IOException { + return IndicesPrivileges.fromXContent(parser); + } + + @Override + protected boolean supportsUnknownFields() { + return false; + } +} diff --git a/docs/java-rest/high-level/security/put-role.asciidoc b/docs/java-rest/high-level/security/put-role.asciidoc new file mode 100644 index 0000000000000..68c1f5d69d470 --- /dev/null +++ b/docs/java-rest/high-level/security/put-role.asciidoc @@ -0,0 +1,37 @@ + +-- +:api: put-role +:request: PutRoleRequest +:response: PutRoleResponse +-- + +[id="{upid}-{api}"] +=== Put Role API + +[id="{upid}-{api}-request"] +==== Put Role Request + +The +{request}+ class is used to create or update a role in the Native Roles +Store. The request contains a single role, which encapsulates privileges over +resources. A role can be assigned to an user using the +<<{upid}-put-role-mapping, Put Role Mapping API>>. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-request] +-------------------------------------------------- + +include::../execution.asciidoc[] + +[id="{upid}-{api}-response"] +==== Put Role Response + +The returned +{response}+ contains a single field, `created`. This field +serves as an indication if the role was created or if an existing entry was +updated. + +["source","java",subs="attributes,callouts,macros"] +-------------------------------------------------- +include-tagged::{doc-tests-file}[{api}-response] +-------------------------------------------------- +<1> `created` is a boolean indicating whether the role was created or updated diff --git a/docs/java-rest/high-level/supported-apis.asciidoc b/docs/java-rest/high-level/supported-apis.asciidoc index a25205d9533ff..82b868b59d6b3 100644 --- a/docs/java-rest/high-level/supported-apis.asciidoc +++ b/docs/java-rest/high-level/supported-apis.asciidoc @@ -385,6 +385,7 @@ The Java High Level REST Client supports the following Security APIs: * <> * <> * <> +* <<{upid}-put-role>> * <<{upid}-get-roles>> * <> * <<{upid}-clear-roles-cache>> @@ -406,6 +407,7 @@ include::security/delete-user.asciidoc[] include::security/enable-user.asciidoc[] include::security/disable-user.asciidoc[] include::security/change-password.asciidoc[] +include::security/put-role.asciidoc[] include::security/get-roles.asciidoc[] include::security/delete-role.asciidoc[] include::security/delete-privileges.asciidoc[]