HLRest: model role and privileges (#35128)

The following "user privileges" objects have been created
on the client side:

* ApplicationResourcePrivileges
* IndicesPrivileges
* GlobalOperationPrivilege
* GlobalPrivileges

as well as the aggregating `Role` entity.
This commit is contained in:
Albert Zaharovits 2018-11-11 17:13:25 +02:00 committed by GitHub
parent eb8f346204
commit 6050deb6f0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 1238 additions and 0 deletions

View File

@ -0,0 +1,156 @@
/*
* 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.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
/**
* Represents privileges over resources that are scoped under an application.
* The application, resources and privileges are completely managed by the
* client and can be arbitrary string identifiers. Elasticsearch is not
* concerned by any resources under an application scope.
*/
public final class ApplicationResourcePrivileges implements ToXContentObject {
private static final ParseField APPLICATION = new ParseField("application");
private static final ParseField PRIVILEGES = new ParseField("privileges");
private static final ParseField RESOURCES = new ParseField("resources");
@SuppressWarnings("unchecked")
static final ConstructingObjectParser<ApplicationResourcePrivileges, Void> PARSER = new ConstructingObjectParser<>(
"application_privileges", false, constructorObjects -> {
// 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.
int i = 0;
final String application = (String) constructorObjects[i++];
final Collection<String> privileges = (Collection<String>) constructorObjects[i++];
final Collection<String> resources = (Collection<String>) constructorObjects[i];
return new ApplicationResourcePrivileges(application, privileges, resources);
});
static {
PARSER.declareString(constructorArg(), APPLICATION);
PARSER.declareStringArray(constructorArg(), PRIVILEGES);
PARSER.declareStringArray(constructorArg(), RESOURCES);
}
private final String application;
private final Set<String> privileges;
private final Set<String> resources;
/**
* Constructs privileges for resources under an application scope.
*
* @param application
* The application name. This identifier is completely under the
* clients control.
* @param privileges
* The privileges names. Cannot be null or empty. Privilege
* identifiers are completely under the clients control.
* @param resources
* The resources names. Cannot be null or empty. Resource identifiers
* are completely under the clients control.
*/
public ApplicationResourcePrivileges(String application, Collection<String> privileges, Collection<String> resources) {
if (Strings.isNullOrEmpty(application)) {
throw new IllegalArgumentException("application privileges must have an application name");
}
if (null == privileges || privileges.isEmpty()) {
throw new IllegalArgumentException("application privileges must define at least one privilege");
}
if (null == resources || resources.isEmpty()) {
throw new IllegalArgumentException("application privileges must refer to at least one resource");
}
this.application = application;
this.privileges = Collections.unmodifiableSet(new HashSet<>(privileges));
this.resources = Collections.unmodifiableSet(new HashSet<>(resources));
}
public String getApplication() {
return application;
}
public Set<String> getResources() {
return this.resources;
}
public Set<String> getPrivileges() {
return this.privileges;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
ApplicationResourcePrivileges that = (ApplicationResourcePrivileges) o;
return application.equals(that.application)
&& privileges.equals(that.privileges)
&& resources.equals(that.resources);
}
@Override
public int hashCode() {
return Objects.hash(application, privileges, resources);
}
@Override
public String toString() {
try {
return XContentHelper.toXContent(this, XContentType.JSON, true).utf8ToString();
} catch (IOException e) {
throw new RuntimeException("Unexpected", e);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(APPLICATION.getPreferredName(), application);
builder.field(PRIVILEGES.getPreferredName(), privileges);
builder.field(RESOURCES.getPreferredName(), resources);
return builder.endObject();
}
public static ApplicationResourcePrivileges fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
}

View File

@ -0,0 +1,99 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.client.security.user.privileges;
import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
/**
* Represents generic global cluster privileges that can be scoped by categories
* and then further by operations. The privilege's syntactic and semantic
* meaning is specific to each category and operation; there is no general
* definition template. It is not permitted to define different privileges under
* the same category and operation.
*/
public class GlobalOperationPrivilege {
private final String category;
private final String operation;
private final Map<String, Object> privilege;
/**
* Constructs privileges under a specific {@code category} and for some
* {@code operation}. The privilege definition is flexible, it is a {@code Map},
* and the semantics is bound to the {@code category} and {@code operation}.
*
* @param category
* The category of the privilege.
* @param operation
* The operation of the privilege.
* @param privilege
* The privilege definition.
*/
public GlobalOperationPrivilege(String category, String operation, Map<String, Object> privilege) {
this.category = Objects.requireNonNull(category);
this.operation = Objects.requireNonNull(operation);
if (privilege == null || privilege.isEmpty()) {
throw new IllegalArgumentException("Privileges cannot be empty or null");
}
this.privilege = Collections.unmodifiableMap(privilege);
}
public String getCategory() {
return category;
}
public String getOperation() {
return operation;
}
public Map<String, Object> getRaw() {
return privilege;
}
public static GlobalOperationPrivilege fromXContent(String category, String operation, XContentParser parser) throws IOException {
// parser is still placed on the field name, advance to next token (field value)
assert parser.currentToken().equals(XContentParser.Token.FIELD_NAME);
parser.nextToken();
return new GlobalOperationPrivilege(category, operation, parser.map());
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || (false == this instanceof GlobalOperationPrivilege)) {
return false;
}
final GlobalOperationPrivilege that = (GlobalOperationPrivilege) o;
return category.equals(that.category) && operation.equals(that.operation) && privilege.equals(that.privilege);
}
@Override
public int hashCode() {
return Objects.hash(category, operation, privilege);
}
}

View File

@ -0,0 +1,137 @@
/*
* 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.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
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;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
/**
* Represents global privileges. "Global Privilege" is a mantra for granular
* generic cluster privileges. These privileges are organized into categories.
* Elasticsearch defines the set of categories. Under each category there are
* operations that are under the clients jurisdiction. The privilege is hence
* defined under an operation under a category.
*/
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<String> CATEGORIES = Collections.unmodifiableList(Arrays.asList("application"));
@SuppressWarnings("unchecked")
static final ConstructingObjectParser<GlobalPrivileges, Void> PARSER = new ConstructingObjectParser<>("global_category_privileges",
false, constructorObjects -> {
// ignore_unknown_fields is irrelevant here anyway, but let's keep it to false
// because this conveys strictness (woop woop)
return new GlobalPrivileges((Collection<GlobalOperationPrivilege>) constructorObjects[0]);
});
static {
for (final String category : CATEGORIES) {
PARSER.declareNamedObjects(optionalConstructorArg(),
(parser, context, operation) -> GlobalOperationPrivilege.fromXContent(category, operation, parser),
new ParseField(category));
}
}
private final Set<? extends GlobalOperationPrivilege> privileges;
// same data as in privileges but broken down by categories; internally, it is
// easier to work with this structure
private final Map<String, List<GlobalOperationPrivilege>> privilegesByCategoryMap;
/**
* Constructs global privileges by bundling the set of privileges.
*
* @param privileges
* The privileges under a category and for an operation in that category.
*/
public GlobalPrivileges(Collection<? extends GlobalOperationPrivilege> privileges) {
if (privileges == null || privileges.isEmpty()) {
throw new IllegalArgumentException("Privileges cannot be empty or null");
}
// duplicates are just ignored
this.privileges = Collections.unmodifiableSet(new HashSet<>(Objects.requireNonNull(privileges)));
this.privilegesByCategoryMap = Collections
.unmodifiableMap(this.privileges.stream().collect(Collectors.groupingBy(GlobalOperationPrivilege::getCategory)));
for (final Map.Entry<String, List<GlobalOperationPrivilege>> privilegesByCategory : privilegesByCategoryMap.entrySet()) {
// all operations for a specific category
final Set<String> allOperations = privilegesByCategory.getValue().stream().map(p -> p.getOperation())
.collect(Collectors.toSet());
if (allOperations.size() != privilegesByCategory.getValue().size()) {
throw new IllegalArgumentException("Different privileges for the same category and operation are not permitted");
}
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
for (final Map.Entry<String, List<GlobalOperationPrivilege>> privilegesByCategory : this.privilegesByCategoryMap.entrySet()) {
builder.startObject(privilegesByCategory.getKey());
for (final GlobalOperationPrivilege privilege : privilegesByCategory.getValue()) {
builder.field(privilege.getOperation(), privilege.getRaw());
}
builder.endObject();
}
return builder.endObject();
}
public static GlobalPrivileges fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
public Set<? extends GlobalOperationPrivilege> getPrivileges() {
return privileges;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || this.getClass() != o.getClass()) {
return false;
}
final GlobalPrivileges that = (GlobalPrivileges) o;
return privileges.equals(that.privileges);
}
@Override
public int hashCode() {
return Objects.hash(privileges);
}
}

View File

@ -0,0 +1,309 @@
/*
* 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.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.collect.Tuple;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
/**
* Represents privileges over indices. There is a canonical set of privilege
* names (eg. {@code IndicesPrivileges#READ_PRIVILEGE_NAME}) but there is
* flexibility in the definition of finer grained, more specialized, privileges.
* This also encapsulates field and document level security privileges. These
* allow to control what fields or documents are readable or queryable.
*/
public final class IndicesPrivileges implements ToXContentObject {
public static final ParseField NAMES = new ParseField("names");
public static final ParseField PRIVILEGES = new ParseField("privileges");
public static final ParseField FIELD_PERMISSIONS = new ParseField("field_security");
public static final ParseField GRANT_FIELDS = new ParseField("grant");
public static final ParseField EXCEPT_FIELDS = new ParseField("except");
public static final ParseField QUERY = new ParseField("query");
@SuppressWarnings("unchecked")
static final ConstructingObjectParser<IndicesPrivileges, Void> PARSER =
new ConstructingObjectParser<>("indices_privileges", false, constructorObjects -> {
int i = 0;
final Collection<String> indices = (Collection<String>) constructorObjects[i++];
final Collection<String> privileges = (Collection<String>) constructorObjects[i++];
final Tuple<Collection<String>, Collection<String>> fields =
(Tuple<Collection<String>, Collection<String>>) constructorObjects[i++];
final Collection<String> grantFields = fields != null ? fields.v1() : null;
final Collection<String> exceptFields = fields != null ? fields.v2() : null;
final String query = (String) constructorObjects[i];
return new IndicesPrivileges(indices, privileges, grantFields, exceptFields, query);
});
static {
@SuppressWarnings("unchecked")
final ConstructingObjectParser<Tuple<Collection<String>, Collection<String>>, Void> fls_parser =
new ConstructingObjectParser<>( "field_level_parser", false, constructorObjects -> {
int i = 0;
final Collection<String> grantFields = (Collection<String>) constructorObjects[i++];
final Collection<String> exceptFields = (Collection<String>) constructorObjects[i];
return new Tuple<>(grantFields, exceptFields);
});
fls_parser.declareStringArray(optionalConstructorArg(), GRANT_FIELDS);
fls_parser.declareStringArray(optionalConstructorArg(), EXCEPT_FIELDS);
PARSER.declareStringArray(constructorArg(), NAMES);
PARSER.declareStringArray(constructorArg(), PRIVILEGES);
PARSER.declareObject(optionalConstructorArg(), fls_parser, FIELD_PERMISSIONS);
PARSER.declareStringOrNull(optionalConstructorArg(), QUERY);
}
private final Set<String> indices;
private final Set<String> privileges;
// null or singleton '*' means all fields are granted, empty means no fields are granted
private final @Nullable Set<String> grantedFields;
// null or empty means no fields are denied
private final @Nullable Set<String> deniedFields;
// missing query means all documents, i.e. no restrictions
private final @Nullable String query;
private IndicesPrivileges(Collection<String> indices, Collection<String> privileges, @Nullable Collection<String> grantedFields,
@Nullable Collection<String> deniedFields, @Nullable String query) {
if (null == indices || indices.isEmpty()) {
throw new IllegalArgumentException("indices privileges must refer to at least one index name or index name pattern");
}
if (null == privileges || privileges.isEmpty()) {
throw new IllegalArgumentException("indices privileges must define at least one privilege");
}
this.indices = Collections.unmodifiableSet(new HashSet<>(indices));
this.privileges = Collections.unmodifiableSet(new HashSet<>(privileges));
// unspecified granted fields means no restriction
this.grantedFields = grantedFields == null ? null : Collections.unmodifiableSet(new HashSet<>(grantedFields));
// unspecified denied fields means no restriction
this.deniedFields = deniedFields == null ? null : Collections.unmodifiableSet(new HashSet<>(deniedFields));
this.query = query;
}
/**
* The indices names covered by the privileges.
*/
public Set<String> getIndices() {
return this.indices;
}
/**
* The privileges acting over indices. There is a canonical predefined set of
* such privileges, but the {@code String} datatype allows for flexibility in defining
* finer grained privileges.
*/
public Set<String> getPrivileges() {
return this.privileges;
}
/**
* The document fields that can be read or queried. Can be null, in this case
* all the document's fields are granted access to. Can also be empty, in which
* case no fields are granted access to.
*/
public @Nullable Set<String> getGrantedFields() {
return this.grantedFields;
}
/**
* The document fields that cannot be accessed or queried. Can be null or empty,
* in which case no fields are denied.
*/
public @Nullable Set<String> getDeniedFields() {
return this.deniedFields;
}
/**
* A query limiting the visible documents in the indices. Can be null, in which
* case all documents are visible.
*/
public @Nullable String getQuery() {
return this.query;
}
/**
* If {@code true} some documents might not be visible. Only the documents
* matching {@code query} will be readable.
*/
public boolean isUsingDocumentLevelSecurity() {
return query != null;
}
/**
* If {@code true} some document fields might not be visible.
*/
public boolean isUsingFieldLevelSecurity() {
return limitsGrantedFields() || hasDeniedFields();
}
private boolean hasDeniedFields() {
return deniedFields != null && false == deniedFields.isEmpty();
}
private boolean limitsGrantedFields() {
// we treat just '*' as no FLS since that's what the UI defaults to
if (grantedFields == null || (grantedFields.size() == 1 && grantedFields.iterator().next().equals("*"))) {
return false;
}
return true;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
IndicesPrivileges that = (IndicesPrivileges) o;
return indices.equals(that.indices)
&& privileges.equals(that.privileges)
&& Objects.equals(grantedFields, that.grantedFields)
&& Objects.equals(deniedFields, that.deniedFields)
&& Objects.equals(query, that.query);
}
@Override
public int hashCode() {
return Objects.hash(indices, privileges, grantedFields, deniedFields, query);
}
@Override
public String toString() {
try {
return XContentHelper.toXContent(this, XContentType.JSON, true).utf8ToString();
} catch (IOException e) {
throw new RuntimeException("Unexpected", e);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(NAMES.getPreferredName(), indices);
builder.field(PRIVILEGES.getPreferredName(), privileges);
if (isUsingFieldLevelSecurity()) {
builder.startObject(FIELD_PERMISSIONS.getPreferredName());
if (grantedFields != null) {
builder.field(GRANT_FIELDS.getPreferredName(), grantedFields);
}
if (hasDeniedFields()) {
builder.field(EXCEPT_FIELDS.getPreferredName(), deniedFields);
}
builder.endObject();
}
if (isUsingDocumentLevelSecurity()) {
builder.field("query", query);
}
return builder.endObject();
}
public static IndicesPrivileges fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private @Nullable Collection<String> indices = null;
private @Nullable Collection<String> privileges = null;
private @Nullable Collection<String> grantedFields = null;
private @Nullable Collection<String> deniedFields = null;
private @Nullable String query = null;
private Builder() {
}
public Builder indices(String... indices) {
return indices(Arrays.asList(Objects.requireNonNull(indices, "indices required")));
}
public Builder indices(Collection<String> indices) {
this.indices = Objects.requireNonNull(indices, "indices required");
return this;
}
public Builder privileges(String... privileges) {
return privileges(Arrays.asList(Objects.requireNonNull(privileges, "privileges required")));
}
public Builder privileges(Collection<String> privileges) {
this.privileges = Objects.requireNonNull(privileges, "privileges required");
return this;
}
public Builder grantedFields(@Nullable String... grantedFields) {
if (grantedFields == null) {
this.grantedFields = null;
return this;
}
return grantedFields(Arrays.asList(grantedFields));
}
public Builder grantedFields(@Nullable Collection<String> grantedFields) {
this.grantedFields = grantedFields;
return this;
}
public Builder deniedFields(@Nullable String... deniedFields) {
if (deniedFields == null) {
this.deniedFields = null;
return this;
}
return deniedFields(Arrays.asList(deniedFields));
}
public Builder deniedFields(@Nullable Collection<String> deniedFields) {
this.deniedFields = deniedFields;
return this;
}
public Builder query(@Nullable String query) {
this.query = query;
return this;
}
public IndicesPrivileges build() {
return new IndicesPrivileges(indices, privileges, grantedFields, deniedFields, query);
}
}
}

View File

@ -0,0 +1,56 @@
/*
* 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 java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
/**
* Represents the privilege to "manage" certain applications. The "manage"
* privilege is actually defined outside of Elasticsearch.
*/
public class ManageApplicationPrivilege extends GlobalOperationPrivilege {
private static final String CATEGORY = "application";
private static final String OPERATION = "manage";
private static final String KEY = "applications";
public ManageApplicationPrivilege(Collection<String> applications) {
super(CATEGORY, OPERATION, Collections.singletonMap(KEY, new HashSet<String>(Objects.requireNonNull(applications))));
}
@SuppressWarnings("unchecked")
public Set<String> getManagedApplications() {
return (Set<String>)getRaw().get(KEY);
}
@Override
public boolean equals(Object o) {
return super.equals(o);
}
@Override
public int hashCode() {
return super.hashCode();
}
}

View File

@ -0,0 +1,310 @@
/*
* 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.Nullable;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
/**
* Represents an aggregation of privileges. This does not have a name
* identifier.
*/
public final class Role implements ToXContentObject {
public static final ParseField CLUSTER = new ParseField("cluster");
public static final ParseField GLOBAL = new ParseField("global");
public static final ParseField INDICES = new ParseField("indices");
public static final ParseField APPLICATIONS = new ParseField("applications");
public static final ParseField RUN_AS = new ParseField("run_as");
public static final ParseField METADATA = new ParseField("metadata");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<Role, Void> PARSER = new ConstructingObjectParser<>("role_descriptor", false,
constructorObjects -> {
// 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.
int i = 0;
final Collection<String> clusterPrivileges = (Collection<String>) constructorObjects[i++];
final GlobalPrivileges globalApplicationPrivileges = (GlobalPrivileges) constructorObjects[i++];
final Collection<IndicesPrivileges> indicesPrivileges = (Collection<IndicesPrivileges>) constructorObjects[i++];
final Collection<ApplicationResourcePrivileges> applicationResourcePrivileges =
(Collection<ApplicationResourcePrivileges>) constructorObjects[i++];
final Collection<String> runAsPrivilege = (Collection<String>) constructorObjects[i++];
final Map<String, Object> metadata = (Map<String, Object>) constructorObjects[i];
return new Role(clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
runAsPrivilege, metadata);
});
static {
PARSER.declareStringArray(optionalConstructorArg(), CLUSTER);
PARSER.declareObject(optionalConstructorArg(), GlobalPrivileges.PARSER, GLOBAL);
PARSER.declareFieldArray(optionalConstructorArg(), IndicesPrivileges.PARSER, INDICES, ValueType.OBJECT_ARRAY);
PARSER.declareFieldArray(optionalConstructorArg(), ApplicationResourcePrivileges.PARSER, APPLICATIONS, ValueType.OBJECT_ARRAY);
PARSER.declareStringArray(optionalConstructorArg(), RUN_AS);
PARSER.declareObject(constructorArg(), (parser, c) -> parser.map(), METADATA);
}
private final Set<String> clusterPrivileges;
private final @Nullable GlobalPrivileges globalApplicationPrivileges;
private final Set<IndicesPrivileges> indicesPrivileges;
private final Set<ApplicationResourcePrivileges> applicationResourcePrivileges;
private final Set<String> runAsPrivilege;
private final Map<String, Object> metadata;
private Role(@Nullable Collection<String> clusterPrivileges, @Nullable GlobalPrivileges globalApplicationPrivileges,
@Nullable Collection<IndicesPrivileges> indicesPrivileges,
@Nullable Collection<ApplicationResourcePrivileges> applicationResourcePrivileges, @Nullable Collection<String> runAsPrivilege,
@Nullable Map<String, Object> metadata) {
// no cluster privileges are granted unless otherwise specified
this.clusterPrivileges = Collections
.unmodifiableSet(clusterPrivileges != null ? new HashSet<>(clusterPrivileges) : Collections.emptySet());
this.globalApplicationPrivileges = globalApplicationPrivileges;
// no indices privileges are granted unless otherwise specified
this.indicesPrivileges = Collections
.unmodifiableSet(indicesPrivileges != null ? new HashSet<>(indicesPrivileges) : Collections.emptySet());
// no application resource privileges are granted unless otherwise specified
this.applicationResourcePrivileges = Collections.unmodifiableSet(
applicationResourcePrivileges != null ? new HashSet<>(applicationResourcePrivileges) : Collections.emptySet());
// 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();
}
public Set<String> getClusterPrivileges() {
return clusterPrivileges;
}
public GlobalPrivileges getGlobalApplicationPrivileges() {
return globalApplicationPrivileges;
}
public Set<IndicesPrivileges> getIndicesPrivileges() {
return indicesPrivileges;
}
public Set<ApplicationResourcePrivileges> getApplicationResourcePrivileges() {
return applicationResourcePrivileges;
}
public Set<String> getRunAsPrivilege() {
return runAsPrivilege;
}
public Map<String, Object> getMetadata() {
return metadata;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Role that = (Role) o;
return clusterPrivileges.equals(that.clusterPrivileges)
&& Objects.equals(globalApplicationPrivileges, that.globalApplicationPrivileges)
&& indicesPrivileges.equals(that.indicesPrivileges)
&& applicationResourcePrivileges.equals(that.applicationResourcePrivileges)
&& runAsPrivilege.equals(that.runAsPrivilege)
&& metadata.equals(that.metadata);
}
@Override
public int hashCode() {
return Objects.hash(clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
runAsPrivilege, metadata);
}
@Override
public String toString() {
try {
return XContentHelper.toXContent(this, XContentType.JSON, true).utf8ToString();
} catch (IOException e) {
throw new RuntimeException("Unexpected", e);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (false == clusterPrivileges.isEmpty()) {
builder.field(CLUSTER.getPreferredName(), clusterPrivileges);
}
if (null != globalApplicationPrivileges) {
builder.field(GLOBAL.getPreferredName(), globalApplicationPrivileges);
}
if (false == indicesPrivileges.isEmpty()) {
builder.field(INDICES.getPreferredName(), indicesPrivileges);
}
if (false == applicationResourcePrivileges.isEmpty()) {
builder.field(APPLICATIONS.getPreferredName(), applicationResourcePrivileges);
}
if (false == runAsPrivilege.isEmpty()) {
builder.field(RUN_AS.getPreferredName(), runAsPrivilege);
}
if (false == metadata.isEmpty()) {
builder.field(METADATA.getPreferredName(), metadata);
}
return builder.endObject();
}
public static Role fromXContent(XContentParser parser) {
return PARSER.apply(parser, null);
}
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private @Nullable Collection<String> clusterPrivileges = null;
private @Nullable GlobalPrivileges globalApplicationPrivileges = null;
private @Nullable Collection<IndicesPrivileges> indicesPrivileges = null;
private @Nullable Collection<ApplicationResourcePrivileges> applicationResourcePrivileges = null;
private @Nullable Collection<String> runAsPrivilege = null;
private @Nullable Map<String, Object> metadata = null;
private Builder() {
}
public Builder clusterPrivileges(String... clusterPrivileges) {
return clusterPrivileges(Arrays
.asList(Objects.requireNonNull(clusterPrivileges, "Cluster privileges cannot be null. Pass an empty array instead.")));
}
public Builder clusterPrivileges(Collection<String> clusterPrivileges) {
this.clusterPrivileges = Objects.requireNonNull(clusterPrivileges,
"Cluster privileges cannot be null. Pass an empty collection instead.");
return this;
}
public Builder glabalApplicationPrivileges(GlobalPrivileges globalApplicationPrivileges) {
this.globalApplicationPrivileges = globalApplicationPrivileges;
return this;
}
public Builder indicesPrivileges(IndicesPrivileges... indicesPrivileges) {
return indicesPrivileges(Arrays
.asList(Objects.requireNonNull(indicesPrivileges, "Indices privileges cannot be null. Pass an empty array instead.")));
}
public Builder indicesPrivileges(Collection<IndicesPrivileges> indicesPrivileges) {
this.indicesPrivileges = Objects.requireNonNull(indicesPrivileges,
"Indices privileges cannot be null. Pass an empty collection instead.");
return this;
}
public Builder applicationResourcePrivileges(ApplicationResourcePrivileges... applicationResourcePrivileges) {
return applicationResourcePrivileges(Arrays.asList(Objects.requireNonNull(applicationResourcePrivileges,
"Application resource privileges cannot be null. Pass an empty array instead.")));
}
public Builder applicationResourcePrivileges(Collection<ApplicationResourcePrivileges> applicationResourcePrivileges) {
this.applicationResourcePrivileges = Objects.requireNonNull(applicationResourcePrivileges,
"Application resource privileges cannot be null. Pass an empty collection instead.");
return this;
}
public Builder runAsPrivilege(String... runAsPrivilege) {
return runAsPrivilege(Arrays
.asList(Objects.requireNonNull(runAsPrivilege, "Run as privilege cannot be null. Pass an empty array instead.")));
}
public Builder runAsPrivilege(Collection<String> runAsPrivilege) {
this.runAsPrivilege = Objects.requireNonNull(runAsPrivilege,
"Run as privilege cannot be null. Pass an empty collection instead.");
return this;
}
public Builder metadata(Map<String, Object> metadata) {
this.metadata = Objects.requireNonNull(metadata, "Metadata cannot be null. Pass an empty map instead.");
return this;
}
public Role build() {
return new Role(clusterPrivileges, globalApplicationPrivileges, indicesPrivileges, applicationResourcePrivileges,
runAsPrivilege, metadata);
}
}
/**
* Canonical cluster privilege names. There is no enforcement to only use these.
*/
public static class ClusterPrivilegeName {
public static final String NONE = "none";
public static final String ALL = "all";
public static final String MONITOR = "monitor";
public static final String MONITOR_ML = "monitor_ml";
public static final String MONITOR_WATCHER = "monitor_watcher";
public static final String MONITOR_ROLLUP = "monitor_rollup";
public static final String MANAGE = "manage";
public static final String MANAGE_ML = "manage_ml";
public static final String MANAGE_WATCHER = "manage_watcher";
public static final String MANAGE_ROLLUP = "manage_rollup";
public static final String MANAGE_INDEX_TEMPLATES = "manage_index_templates";
public static final String MANAGE_INGEST_PIPELINES = "manage_ingest_pipelines";
public static final String TRANSPORT_CLIENT = "transport_client";
public static final String MANAGE_SECURITY = "manage_security";
public static final String MANAGE_SAML = "manage_saml";
public static final String MANAGE_PIPELINE = "manage_pipeline";
public static final String MANAGE_CCR = "manage_ccr";
public static final String READ_CCR = "read_ccr";
}
/**
* Canonical index privilege names. There is no enforcement to only use these.
*/
public static class IndexPrivilegeName {
public static final String NONE = "none";
public static final String ALL = "all";
public static final String READ = "read";
public static final String READ_CROSS = "read_cross_cluster";
public static final String CREATE = "create";
public static final String INDEX = "index";
public static final String DELETE = "delete";
public static final String WRITE = "write";
public static final String MONITOR = "monitor";
public static final String MANAGE = "manage";
public static final String DELETE_INDEX = "delete_index";
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";
}
}

View File

@ -0,0 +1,77 @@
/*
* 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.Collection;
import java.util.Collections;
import static org.hamcrest.Matchers.is;
public class ApplicationResourcePrivilegesTests extends AbstractXContentTestCase<ApplicationResourcePrivileges> {
@Override
protected ApplicationResourcePrivileges createTestInstance() {
return new ApplicationResourcePrivileges(randomAlphaOfLengthBetween(1, 8),
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 doParseInstance(XContentParser parser) throws IOException {
return ApplicationResourcePrivileges.fromXContent(parser);
}
@Override
protected boolean supportsUnknownFields() {
return false;
}
public void testEmptyApplicationName() {
final String emptyApplicationName = randomBoolean() ? "" : null;
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> new ApplicationResourcePrivileges(emptyApplicationName,
Arrays.asList(randomArray(1, 8, size -> new String[size], () -> randomAlphaOfLengthBetween(1, 8))),
Arrays.asList(randomArray(1, 8, size -> new String[size], () -> randomAlphaOfLengthBetween(1, 8)))));
assertThat(e.getMessage(), is("application privileges must have an application name"));
}
public void testEmptyPrivileges() {
final Collection<String> emptyPrivileges = randomBoolean() ? Collections.emptyList() : null;
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> new ApplicationResourcePrivileges(randomAlphaOfLengthBetween(1, 8),
emptyPrivileges,
Arrays.asList(randomArray(1, 8, size -> new String[size], () -> randomAlphaOfLengthBetween(1, 8)))));
assertThat(e.getMessage(), is("application privileges must define at least one privilege"));
}
public void testEmptyResources() {
final Collection<String> emptyResources = randomBoolean() ? Collections.emptyList() : null;
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> new ApplicationResourcePrivileges(randomAlphaOfLengthBetween(1, 8),
Arrays.asList(randomArray(1, 8, size -> new String[size], () -> randomAlphaOfLengthBetween(1, 8))),
emptyResources));
assertThat(e.getMessage(), is("application privileges must refer to at least one resource"));
}
}

View File

@ -0,0 +1,94 @@
/*
* 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.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static org.hamcrest.Matchers.is;
public class GlobalPrivilegesTests extends AbstractXContentTestCase<GlobalPrivileges> {
private static long idCounter = 0;
@Override
protected GlobalPrivileges createTestInstance() {
final List<GlobalOperationPrivilege> privilegeList = Arrays
.asList(randomArray(1, 4, size -> new GlobalOperationPrivilege[size], () -> buildRandomGlobalScopedPrivilege()));
return new GlobalPrivileges(privilegeList);
}
@Override
protected GlobalPrivileges doParseInstance(XContentParser parser) throws IOException {
return GlobalPrivileges.fromXContent(parser);
}
@Override
protected boolean supportsUnknownFields() {
return false; // true really means inserting bogus privileges
}
public void testEmptyOrNullGlobalOperationPrivilege() {
final Map<String, Object> privilege = randomBoolean() ? null : Collections.emptyMap();
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> new GlobalOperationPrivilege(randomAlphaOfLength(2), randomAlphaOfLength(2), privilege));
assertThat(e.getMessage(), is("Privileges cannot be empty or null"));
}
public void testEmptyOrNullGlobalPrivileges() {
final List<GlobalOperationPrivilege> privileges = randomBoolean() ? null : Collections.emptyList();
final IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> new GlobalPrivileges(privileges));
assertThat(e.getMessage(), is("Privileges cannot be empty or null"));
}
public void testDuplicateGlobalOperationPrivilege() {
final GlobalOperationPrivilege privilege = buildRandomGlobalScopedPrivilege();
// duplicate
final GlobalOperationPrivilege privilege2 = new GlobalOperationPrivilege(privilege.getCategory(), privilege.getOperation(),
new HashMap<>(privilege.getRaw()));
final GlobalPrivileges globalPrivilege = new GlobalPrivileges(Arrays.asList(privilege, privilege2));
assertThat(globalPrivilege.getPrivileges().size(), is(1));
assertThat(globalPrivilege.getPrivileges().iterator().next(), is(privilege));
}
public void testSameScopeGlobalOperationPrivilege() {
final GlobalOperationPrivilege privilege = buildRandomGlobalScopedPrivilege();
final GlobalOperationPrivilege sameOperationPrivilege = new GlobalOperationPrivilege(privilege.getCategory(),
privilege.getOperation(), buildRandomGlobalScopedPrivilege().getRaw());
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
() -> new GlobalPrivileges(Arrays.asList(privilege, sameOperationPrivilege)));
assertThat(e.getMessage(), is("Different privileges for the same category and operation are not permitted"));
}
private static GlobalOperationPrivilege buildRandomGlobalScopedPrivilege() {
final Map<String, Object> 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);
}
}