Support mustache templates in role mappings (#40571)
This adds a new `role_templates` field to role mappings that is an alternative to the existing roles field. These templates are evaluated at runtime to determine which roles should be granted to a user. For example, it is possible to specify: "role_templates": [ { "template":{ "source": "_user_{{username}}" } } ] which would mean that every user is assigned to their own role based on their username. You may not specify both roles and role_templates in the same role mapping. This commit adds support for templates to the role mapping API, the role mapping engine, the Java high level rest client, and Elasticsearch documentation. Due to the lack of caching in our role mapping store, it is currently inefficient to use a large number of templated role mappings. This will be addressed in a future change. Backport of: #39984, #40504
This commit is contained in:
parent
965e311094
commit
2c770ba3cb
|
@ -29,8 +29,10 @@ import org.elasticsearch.common.xcontent.XContentParser;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
|
||||
|
||||
/**
|
||||
* A representation of a single role-mapping.
|
||||
|
@ -42,13 +44,14 @@ public final class ExpressionRoleMapping {
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
static final ConstructingObjectParser<ExpressionRoleMapping, String> PARSER = new ConstructingObjectParser<>("role-mapping", true,
|
||||
(args, name) -> new ExpressionRoleMapping(name, (RoleMapperExpression) args[0], (List<String>) args[1],
|
||||
(Map<String, Object>) args[2], (boolean) args[3]));
|
||||
(args, name) -> new ExpressionRoleMapping(name, (RoleMapperExpression) args[0], (List<String>) args[1],
|
||||
(List<TemplateRoleName>) args[2], (Map<String, Object>) args[3], (boolean) args[4]));
|
||||
|
||||
static {
|
||||
PARSER.declareField(constructorArg(), (parser, context) -> RoleMapperExpressionParser.fromXContent(parser), Fields.RULES,
|
||||
ObjectParser.ValueType.OBJECT);
|
||||
PARSER.declareStringArray(constructorArg(), Fields.ROLES);
|
||||
PARSER.declareStringArray(optionalConstructorArg(), Fields.ROLES);
|
||||
PARSER.declareObjectArray(optionalConstructorArg(), (parser, ctx) -> TemplateRoleName.fromXContent(parser), Fields.ROLE_TEMPLATES);
|
||||
PARSER.declareField(constructorArg(), XContentParser::map, Fields.METADATA, ObjectParser.ValueType.OBJECT);
|
||||
PARSER.declareBoolean(constructorArg(), Fields.ENABLED);
|
||||
}
|
||||
|
@ -56,6 +59,7 @@ public final class ExpressionRoleMapping {
|
|||
private final String name;
|
||||
private final RoleMapperExpression expression;
|
||||
private final List<String> roles;
|
||||
private final List<TemplateRoleName> roleTemplates;
|
||||
private final Map<String, Object> metadata;
|
||||
private final boolean enabled;
|
||||
|
||||
|
@ -70,10 +74,11 @@ public final class ExpressionRoleMapping {
|
|||
* @param enabled a flag when {@code true} signifies the role mapping is active
|
||||
*/
|
||||
public ExpressionRoleMapping(final String name, final RoleMapperExpression expr, final List<String> roles,
|
||||
final Map<String, Object> metadata, boolean enabled) {
|
||||
final List<TemplateRoleName> templates, final Map<String, Object> metadata, boolean enabled) {
|
||||
this.name = name;
|
||||
this.expression = expr;
|
||||
this.roles = Collections.unmodifiableList(roles);
|
||||
this.roles = roles == null ? Collections.emptyList() : Collections.unmodifiableList(roles);
|
||||
this.roleTemplates = templates == null ? Collections.emptyList() : Collections.unmodifiableList(templates);
|
||||
this.metadata = (metadata == null) ? Collections.emptyMap() : Collections.unmodifiableMap(metadata);
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
@ -90,6 +95,10 @@ public final class ExpressionRoleMapping {
|
|||
return roles;
|
||||
}
|
||||
|
||||
public List<TemplateRoleName> getRoleTemplates() {
|
||||
return roleTemplates;
|
||||
}
|
||||
|
||||
public Map<String, Object> getMetadata() {
|
||||
return metadata;
|
||||
}
|
||||
|
@ -99,53 +108,26 @@ public final class ExpressionRoleMapping {
|
|||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + (enabled ? 1231 : 1237);
|
||||
result = prime * result + ((expression == null) ? 0 : expression.hashCode());
|
||||
result = prime * result + ((metadata == null) ? 0 : metadata.hashCode());
|
||||
result = prime * result + ((name == null) ? 0 : name.hashCode());
|
||||
result = prime * result + ((roles == null) ? 0 : roles.hashCode());
|
||||
return result;
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final ExpressionRoleMapping that = (ExpressionRoleMapping) o;
|
||||
return this.enabled == that.enabled &&
|
||||
Objects.equals(this.name, that.name) &&
|
||||
Objects.equals(this.expression, that.expression) &&
|
||||
Objects.equals(this.roles, that.roles) &&
|
||||
Objects.equals(this.roleTemplates, that.roleTemplates) &&
|
||||
Objects.equals(this.metadata, that.metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
final ExpressionRoleMapping other = (ExpressionRoleMapping) obj;
|
||||
if (enabled != other.enabled)
|
||||
return false;
|
||||
if (expression == null) {
|
||||
if (other.expression != null)
|
||||
return false;
|
||||
} else if (!expression.equals(other.expression))
|
||||
return false;
|
||||
if (metadata == null) {
|
||||
if (other.metadata != null)
|
||||
return false;
|
||||
} else if (!metadata.equals(other.metadata))
|
||||
return false;
|
||||
if (name == null) {
|
||||
if (other.name != null)
|
||||
return false;
|
||||
} else if (!name.equals(other.name))
|
||||
return false;
|
||||
if (roles == null) {
|
||||
if (other.roles != null)
|
||||
return false;
|
||||
} else if (!roles.equals(other.roles))
|
||||
return false;
|
||||
return true;
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, expression, roles, roleTemplates, metadata, enabled);
|
||||
}
|
||||
|
||||
public interface Fields {
|
||||
ParseField ROLES = new ParseField("roles");
|
||||
ParseField ROLE_TEMPLATES = new ParseField("role_templates");
|
||||
ParseField ENABLED = new ParseField("enabled");
|
||||
ParseField RULES = new ParseField("rules");
|
||||
ParseField METADATA = new ParseField("metadata");
|
||||
|
|
|
@ -40,22 +40,34 @@ public final class PutRoleMappingRequest implements Validatable, ToXContentObjec
|
|||
private final String name;
|
||||
private final boolean enabled;
|
||||
private final List<String> roles;
|
||||
private final List<TemplateRoleName> roleTemplates;
|
||||
private final RoleMapperExpression rules;
|
||||
|
||||
private final Map<String, Object> metadata;
|
||||
private final RefreshPolicy refreshPolicy;
|
||||
|
||||
@Deprecated
|
||||
public PutRoleMappingRequest(final String name, final boolean enabled, final List<String> roles, final RoleMapperExpression rules,
|
||||
@Nullable final Map<String, Object> metadata, @Nullable final RefreshPolicy refreshPolicy) {
|
||||
@Nullable final Map<String, Object> metadata, @Nullable final RefreshPolicy refreshPolicy) {
|
||||
this(name, enabled, roles, Collections.emptyList(), rules, metadata, refreshPolicy);
|
||||
}
|
||||
|
||||
public PutRoleMappingRequest(final String name, final boolean enabled, final List<String> roles, final List<TemplateRoleName> templates,
|
||||
final RoleMapperExpression rules, @Nullable final Map<String, Object> metadata,
|
||||
@Nullable final RefreshPolicy refreshPolicy) {
|
||||
if (Strings.hasText(name) == false) {
|
||||
throw new IllegalArgumentException("role-mapping name is missing");
|
||||
}
|
||||
this.name = name;
|
||||
this.enabled = enabled;
|
||||
if (roles == null || roles.isEmpty()) {
|
||||
throw new IllegalArgumentException("role-mapping roles are missing");
|
||||
this.roles = Collections.unmodifiableList(Objects.requireNonNull(roles, "role-mapping roles cannot be null"));
|
||||
this.roleTemplates = Collections.unmodifiableList(Objects.requireNonNull(templates, "role-mapping role_templates cannot be null"));
|
||||
if (this.roles.isEmpty() && this.roleTemplates.isEmpty()) {
|
||||
throw new IllegalArgumentException("in a role-mapping, one of roles or role_templates is required");
|
||||
}
|
||||
if (this.roles.isEmpty() == false && this.roleTemplates.isEmpty() == false) {
|
||||
throw new IllegalArgumentException("in a role-mapping, cannot specify both roles and role_templates");
|
||||
}
|
||||
this.roles = Collections.unmodifiableList(roles);
|
||||
this.rules = Objects.requireNonNull(rules, "role-mapping rules are missing");
|
||||
this.metadata = (metadata == null) ? Collections.emptyMap() : metadata;
|
||||
this.refreshPolicy = (refreshPolicy == null) ? RefreshPolicy.getDefault() : refreshPolicy;
|
||||
|
@ -73,6 +85,10 @@ public final class PutRoleMappingRequest implements Validatable, ToXContentObjec
|
|||
return roles;
|
||||
}
|
||||
|
||||
public List<TemplateRoleName> getRoleTemplates() {
|
||||
return roleTemplates;
|
||||
}
|
||||
|
||||
public RoleMapperExpression getRules() {
|
||||
return rules;
|
||||
}
|
||||
|
@ -87,7 +103,7 @@ public final class PutRoleMappingRequest implements Validatable, ToXContentObjec
|
|||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, enabled, refreshPolicy, roles, rules, metadata);
|
||||
return Objects.hash(name, enabled, refreshPolicy, roles, roleTemplates, rules, metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -104,11 +120,12 @@ public final class PutRoleMappingRequest implements Validatable, ToXContentObjec
|
|||
final PutRoleMappingRequest other = (PutRoleMappingRequest) obj;
|
||||
|
||||
return (enabled == other.enabled) &&
|
||||
(refreshPolicy == other.refreshPolicy) &&
|
||||
Objects.equals(name, other.name) &&
|
||||
Objects.equals(roles, other.roles) &&
|
||||
Objects.equals(rules, other.rules) &&
|
||||
Objects.equals(metadata, other.metadata);
|
||||
(refreshPolicy == other.refreshPolicy) &&
|
||||
Objects.equals(name, other.name) &&
|
||||
Objects.equals(roles, other.roles) &&
|
||||
Objects.equals(roleTemplates, other.roleTemplates) &&
|
||||
Objects.equals(rules, other.rules) &&
|
||||
Objects.equals(metadata, other.metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -116,9 +133,9 @@ public final class PutRoleMappingRequest implements Validatable, ToXContentObjec
|
|||
builder.startObject();
|
||||
builder.field("enabled", enabled);
|
||||
builder.field("roles", roles);
|
||||
builder.field("role_templates", roleTemplates);
|
||||
builder.field("rules", rules);
|
||||
builder.field("metadata", metadata);
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* 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.Strings;
|
||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentParserUtils;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
|
||||
|
||||
/**
|
||||
* A role name that uses a dynamic template.
|
||||
*/
|
||||
public class TemplateRoleName implements ToXContentObject {
|
||||
|
||||
private static final ConstructingObjectParser<TemplateRoleName, Void> PARSER = new ConstructingObjectParser<>("template-role-name",
|
||||
true, args -> new TemplateRoleName((String) args[0], (Format) args[1]));
|
||||
|
||||
static {
|
||||
PARSER.declareString(ConstructingObjectParser.constructorArg(), Fields.TEMPLATE);
|
||||
PARSER.declareField(optionalConstructorArg(), Format::fromXContent, Fields.FORMAT, ObjectParser.ValueType.STRING);
|
||||
}
|
||||
private final String template;
|
||||
private final Format format;
|
||||
|
||||
public TemplateRoleName(String template, Format format) {
|
||||
this.template = Objects.requireNonNull(template);
|
||||
this.format = Objects.requireNonNull(format);
|
||||
}
|
||||
|
||||
public TemplateRoleName(Map<String, Object> template, Format format) throws IOException {
|
||||
this(Strings.toString(XContentBuilder.builder(XContentType.JSON.xContent()).map(template)), format);
|
||||
}
|
||||
|
||||
public String getTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
public Format getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final TemplateRoleName that = (TemplateRoleName) o;
|
||||
return Objects.equals(this.template, that.template) &&
|
||||
this.format == that.format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(template, format);
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject()
|
||||
.field(Fields.TEMPLATE.getPreferredName(), template)
|
||||
.field(Fields.FORMAT.getPreferredName(), format.name().toLowerCase(Locale.ROOT))
|
||||
.endObject();
|
||||
}
|
||||
|
||||
static TemplateRoleName fromXContent(XContentParser parser) throws IOException {
|
||||
XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.currentToken(), parser::getTokenLocation);
|
||||
return PARSER.parse(parser, null);
|
||||
}
|
||||
|
||||
|
||||
public enum Format {
|
||||
STRING, JSON;
|
||||
|
||||
private static Format fromXContent(XContentParser parser) throws IOException {
|
||||
XContentParserUtils.ensureExpectedToken(XContentParser.Token.VALUE_STRING, parser.currentToken(), parser::getTokenLocation);
|
||||
return Format.valueOf(parser.text().toUpperCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
|
||||
public interface Fields {
|
||||
ParseField TEMPLATE = new ParseField("template");
|
||||
ParseField FORMAT = new ParseField("format");
|
||||
}
|
||||
|
||||
}
|
|
@ -141,8 +141,8 @@ public class SecurityRequestConvertersTests extends ESTestCase {
|
|||
.addExpression(FieldRoleMapperExpression.ofUsername(username))
|
||||
.addExpression(FieldRoleMapperExpression.ofGroups(groupname))
|
||||
.build();
|
||||
final PutRoleMappingRequest putRoleMappingRequest = new PutRoleMappingRequest(roleMappingName, true, Collections.singletonList(
|
||||
rolename), rules, null, refreshPolicy);
|
||||
final PutRoleMappingRequest putRoleMappingRequest = new PutRoleMappingRequest(roleMappingName, true,
|
||||
Collections.singletonList(rolename), Collections.emptyList(), rules, null, refreshPolicy);
|
||||
|
||||
final Request request = SecurityRequestConverters.putRoleMapping(putRoleMappingRequest);
|
||||
|
||||
|
|
|
@ -75,6 +75,7 @@ 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.TemplateRoleName;
|
||||
import org.elasticsearch.client.security.support.ApiKey;
|
||||
import org.elasticsearch.client.security.support.CertificateInfo;
|
||||
import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression;
|
||||
|
@ -94,6 +95,8 @@ import org.elasticsearch.common.unit.TimeValue;
|
|||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import java.io.IOException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
|
@ -108,9 +111,6 @@ import java.util.Set;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
|
@ -120,6 +120,7 @@ import static org.hamcrest.Matchers.equalTo;
|
|||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.isIn;
|
||||
import static org.hamcrest.Matchers.iterableWithSize;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
|
@ -366,8 +367,8 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
.addExpression(FieldRoleMapperExpression.ofUsername("*"))
|
||||
.addExpression(FieldRoleMapperExpression.ofGroups("cn=admins,dc=example,dc=com"))
|
||||
.build();
|
||||
final PutRoleMappingRequest request = new PutRoleMappingRequest("mapping-example", true, Collections.singletonList("superuser"),
|
||||
rules, null, RefreshPolicy.NONE);
|
||||
final PutRoleMappingRequest request = new PutRoleMappingRequest("mapping-example", true,
|
||||
Collections.singletonList("superuser"), Collections.emptyList(), rules, null, RefreshPolicy.NONE);
|
||||
final PutRoleMappingResponse response = client.security().putRoleMapping(request, RequestOptions.DEFAULT);
|
||||
// end::put-role-mapping-execute
|
||||
// tag::put-role-mapping-response
|
||||
|
@ -381,7 +382,8 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
.addExpression(FieldRoleMapperExpression.ofUsername("*"))
|
||||
.addExpression(FieldRoleMapperExpression.ofGroups("cn=admins,dc=example,dc=com"))
|
||||
.build();
|
||||
final PutRoleMappingRequest request = new PutRoleMappingRequest("mapping-example", true, Collections.singletonList("superuser"),
|
||||
final PutRoleMappingRequest request = new PutRoleMappingRequest("mapping-example", true, Collections.emptyList(),
|
||||
Collections.singletonList(new TemplateRoleName("{\"source\":\"{{username}}\"}", TemplateRoleName.Format.STRING)),
|
||||
rules, null, RefreshPolicy.NONE);
|
||||
// tag::put-role-mapping-execute-listener
|
||||
ActionListener<PutRoleMappingResponse> listener = new ActionListener<PutRoleMappingResponse>() {
|
||||
|
@ -397,25 +399,32 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
};
|
||||
// end::put-role-mapping-execute-listener
|
||||
|
||||
// avoid unused local warning
|
||||
assertNotNull(listener);
|
||||
|
||||
// Replace the empty listener by a blocking listener in test
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
listener = new LatchedActionListener<>(listener, latch);
|
||||
final PlainActionFuture<PutRoleMappingResponse> future = new PlainActionFuture<>();
|
||||
listener = future;
|
||||
|
||||
// tag::put-role-mapping-execute-async
|
||||
client.security().putRoleMappingAsync(request, RequestOptions.DEFAULT, listener); // <1>
|
||||
// end::put-role-mapping-execute-async
|
||||
|
||||
assertTrue(latch.await(30L, TimeUnit.SECONDS));
|
||||
assertThat(future.get(), notNullValue());
|
||||
assertThat(future.get().isCreated(), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
public void testGetRoleMappings() throws Exception {
|
||||
final RestHighLevelClient client = highLevelClient();
|
||||
|
||||
final TemplateRoleName monitoring = new TemplateRoleName("{\"source\":\"monitoring\"}", TemplateRoleName.Format.STRING);
|
||||
final TemplateRoleName template = new TemplateRoleName("{\"source\":\"{{username}}\"}", TemplateRoleName.Format.STRING);
|
||||
|
||||
final RoleMapperExpression rules1 = AnyRoleMapperExpression.builder().addExpression(FieldRoleMapperExpression.ofUsername("*"))
|
||||
.addExpression(FieldRoleMapperExpression.ofGroups("cn=admins,dc=example,dc=com")).build();
|
||||
final PutRoleMappingRequest putRoleMappingRequest1 = new PutRoleMappingRequest("mapping-example-1", true, Collections.singletonList(
|
||||
"superuser"), rules1, null, RefreshPolicy.NONE);
|
||||
final PutRoleMappingRequest putRoleMappingRequest1 = new PutRoleMappingRequest("mapping-example-1", true, Collections.emptyList(),
|
||||
Arrays.asList(monitoring, template), rules1, null, RefreshPolicy.NONE);
|
||||
final PutRoleMappingResponse putRoleMappingResponse1 = client.security().putRoleMapping(putRoleMappingRequest1,
|
||||
RequestOptions.DEFAULT);
|
||||
boolean isCreated1 = putRoleMappingResponse1.isCreated();
|
||||
|
@ -424,8 +433,8 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
"cn=admins,dc=example,dc=com")).build();
|
||||
final Map<String, Object> metadata2 = new HashMap<>();
|
||||
metadata2.put("k1", "v1");
|
||||
final PutRoleMappingRequest putRoleMappingRequest2 = new PutRoleMappingRequest("mapping-example-2", true, Collections.singletonList(
|
||||
"monitoring"), rules2, metadata2, RefreshPolicy.NONE);
|
||||
final PutRoleMappingRequest putRoleMappingRequest2 = new PutRoleMappingRequest("mapping-example-2", true,
|
||||
Arrays.asList("superuser"), Collections.emptyList(), rules2, metadata2, RefreshPolicy.NONE);
|
||||
final PutRoleMappingResponse putRoleMappingResponse2 = client.security().putRoleMapping(putRoleMappingRequest2,
|
||||
RequestOptions.DEFAULT);
|
||||
boolean isCreated2 = putRoleMappingResponse2.isCreated();
|
||||
|
@ -445,7 +454,9 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
assertThat(mappings.get(0).getName(), is("mapping-example-1"));
|
||||
assertThat(mappings.get(0).getExpression(), equalTo(rules1));
|
||||
assertThat(mappings.get(0).getMetadata(), equalTo(Collections.emptyMap()));
|
||||
assertThat(mappings.get(0).getRoles(), contains("superuser"));
|
||||
assertThat(mappings.get(0).getRoles(), iterableWithSize(0));
|
||||
assertThat(mappings.get(0).getRoleTemplates(), iterableWithSize(2));
|
||||
assertThat(mappings.get(0).getRoleTemplates(), containsInAnyOrder(monitoring, template));
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -462,11 +473,13 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
if (roleMapping.getName().equals("mapping-example-1")) {
|
||||
assertThat(roleMapping.getMetadata(), equalTo(Collections.emptyMap()));
|
||||
assertThat(roleMapping.getExpression(), equalTo(rules1));
|
||||
assertThat(roleMapping.getRoles(), contains("superuser"));
|
||||
assertThat(roleMapping.getRoles(), emptyIterable());
|
||||
assertThat(roleMapping.getRoleTemplates(), contains(monitoring, template));
|
||||
} else {
|
||||
assertThat(roleMapping.getMetadata(), equalTo(metadata2));
|
||||
assertThat(roleMapping.getExpression(), equalTo(rules2));
|
||||
assertThat(roleMapping.getRoles(), contains("monitoring"));
|
||||
assertThat(roleMapping.getRoles(), contains("superuser"));
|
||||
assertThat(roleMapping.getRoleTemplates(), emptyIterable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -485,11 +498,13 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
if (roleMapping.getName().equals("mapping-example-1")) {
|
||||
assertThat(roleMapping.getMetadata(), equalTo(Collections.emptyMap()));
|
||||
assertThat(roleMapping.getExpression(), equalTo(rules1));
|
||||
assertThat(roleMapping.getRoles(), contains("superuser"));
|
||||
assertThat(roleMapping.getRoles(), emptyIterable());
|
||||
assertThat(roleMapping.getRoleTemplates(), containsInAnyOrder(monitoring, template));
|
||||
} else {
|
||||
assertThat(roleMapping.getMetadata(), equalTo(metadata2));
|
||||
assertThat(roleMapping.getExpression(), equalTo(rules2));
|
||||
assertThat(roleMapping.getRoles(), contains("monitoring"));
|
||||
assertThat(roleMapping.getRoles(), contains("superuser"));
|
||||
assertThat(roleMapping.getRoleTemplates(), emptyIterable());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1093,8 +1108,8 @@ public class SecurityDocumentationIT extends ESRestHighLevelClientTestCase {
|
|||
{
|
||||
// Create role mappings
|
||||
final RoleMapperExpression rules = FieldRoleMapperExpression.ofUsername("*");
|
||||
final PutRoleMappingRequest request = new PutRoleMappingRequest("mapping-example", true, Collections.singletonList("superuser"),
|
||||
rules, null, RefreshPolicy.NONE);
|
||||
final PutRoleMappingRequest request = new PutRoleMappingRequest("mapping-example", true,
|
||||
Collections.singletonList("superuser"), Collections.emptyList(), rules, null, RefreshPolicy.NONE);
|
||||
final PutRoleMappingResponse response = client.security().putRoleMapping(request, RequestOptions.DEFAULT);
|
||||
boolean isCreated = response.isCreated();
|
||||
assertTrue(isCreated);
|
||||
|
|
|
@ -31,6 +31,7 @@ import java.util.Collections;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
public class ExpressionRoleMappingTests extends ESTestCase {
|
||||
|
@ -59,48 +60,53 @@ public class ExpressionRoleMappingTests extends ESTestCase {
|
|||
public void usedDeprecatedField(String usedName, String replacedWith) {
|
||||
}
|
||||
}, json), "example-role-mapping");
|
||||
final ExpressionRoleMapping expectedRoleMapping = new ExpressionRoleMapping("example-role-mapping", FieldRoleMapperExpression
|
||||
.ofKeyValues("realm.name", "kerb1"), Collections.singletonList("superuser"), null, true);
|
||||
final ExpressionRoleMapping expectedRoleMapping = new ExpressionRoleMapping("example-role-mapping",
|
||||
FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"),
|
||||
singletonList("superuser"), Collections.emptyList(),
|
||||
null, true);
|
||||
assertThat(expressionRoleMapping, equalTo(expectedRoleMapping));
|
||||
}
|
||||
|
||||
public void testEqualsHashCode() {
|
||||
final ExpressionRoleMapping expressionRoleMapping = new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression
|
||||
.ofKeyValues("realm.name", "kerb1"), Collections.singletonList("superuser"), null, true);
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(expressionRoleMapping, (original) -> {
|
||||
return new ExpressionRoleMapping(original.getName(), original.getExpression(), original.getRoles(), original.getMetadata(),
|
||||
original.isEnabled());
|
||||
});
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(expressionRoleMapping, (original) -> {
|
||||
return new ExpressionRoleMapping(original.getName(), original.getExpression(), original.getRoles(), original.getMetadata(),
|
||||
original.isEnabled());
|
||||
}, ExpressionRoleMappingTests::mutateTestItem);
|
||||
final ExpressionRoleMapping expressionRoleMapping = new ExpressionRoleMapping("kerberosmapping",
|
||||
FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"),
|
||||
singletonList("superuser"), Collections.emptyList(),
|
||||
null, true);
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(expressionRoleMapping, original ->
|
||||
new ExpressionRoleMapping(original.getName(), original.getExpression(), original.getRoles(), original.getRoleTemplates(),
|
||||
original.getMetadata(), original.isEnabled()), ExpressionRoleMappingTests::mutateTestItem);
|
||||
}
|
||||
|
||||
private static ExpressionRoleMapping mutateTestItem(ExpressionRoleMapping original) {
|
||||
private static ExpressionRoleMapping mutateTestItem(ExpressionRoleMapping original) throws IOException {
|
||||
ExpressionRoleMapping mutated = null;
|
||||
switch (randomIntBetween(0, 4)) {
|
||||
switch (randomIntBetween(0, 5)) {
|
||||
case 0:
|
||||
mutated = new ExpressionRoleMapping("namechanged", FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"), Collections
|
||||
.singletonList("superuser"), null, true);
|
||||
mutated = new ExpressionRoleMapping("namechanged", FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"),
|
||||
singletonList("superuser"), Collections.emptyList(), null, true);
|
||||
break;
|
||||
case 1:
|
||||
mutated = new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("changed", "changed"), Collections
|
||||
.singletonList("superuser"), null, true);
|
||||
mutated = new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("changed", "changed"),
|
||||
singletonList("superuser"), Collections.emptyList(), null, true);
|
||||
break;
|
||||
case 2:
|
||||
mutated = new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"), Collections
|
||||
.singletonList("changed"), null, true);
|
||||
mutated = new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"),
|
||||
singletonList("changed"), Collections.emptyList(), null, true);
|
||||
break;
|
||||
case 3:
|
||||
Map<String, Object> metadata = new HashMap<>();
|
||||
metadata.put("a", "b");
|
||||
mutated = new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"), Collections
|
||||
.singletonList("superuser"), metadata, true);
|
||||
mutated = new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"),
|
||||
singletonList("superuser"), Collections.emptyList(), metadata, true);
|
||||
break;
|
||||
case 4:
|
||||
mutated = new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"), Collections
|
||||
.singletonList("superuser"), null, false);
|
||||
mutated = new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"),
|
||||
Collections.emptyList(),
|
||||
singletonList(new TemplateRoleName(Collections.singletonMap("source", "superuser"), TemplateRoleName.Format.STRING)),
|
||||
null, true);
|
||||
break;
|
||||
case 5:
|
||||
mutated = new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("realm.name", "kerb1"),
|
||||
singletonList("superuser"), Collections.emptyList(), null, false);
|
||||
break;
|
||||
}
|
||||
return mutated;
|
||||
|
|
|
@ -74,9 +74,10 @@ public class GetRoleMappingsResponseTests extends ESTestCase {
|
|||
}, json));
|
||||
final List<ExpressionRoleMapping> expectedRoleMappingsList = new ArrayList<>();
|
||||
expectedRoleMappingsList.add(new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("realm.name",
|
||||
"kerb1"), Collections.singletonList("superuser"), null, true));
|
||||
"kerb1"), Collections.singletonList("superuser"), Collections.emptyList(), null, true));
|
||||
expectedRoleMappingsList.add(new ExpressionRoleMapping("ldapmapping", FieldRoleMapperExpression.ofGroups(
|
||||
"cn=ipausers,cn=groups,cn=accounts,dc=ipademo,dc=local"), Collections.singletonList("monitoring"), null, false));
|
||||
"cn=ipausers,cn=groups,cn=accounts,dc=ipademo,dc=local"), Collections.singletonList("monitoring"), Collections.emptyList(),
|
||||
null, false));
|
||||
final GetRoleMappingsResponse expectedResponse = new GetRoleMappingsResponse(expectedRoleMappingsList);
|
||||
assertThat(response, equalTo(expectedResponse));
|
||||
}
|
||||
|
@ -84,7 +85,7 @@ public class GetRoleMappingsResponseTests extends ESTestCase {
|
|||
public void testEqualsHashCode() {
|
||||
final List<ExpressionRoleMapping> roleMappingsList = new ArrayList<>();
|
||||
roleMappingsList.add(new ExpressionRoleMapping("kerberosmapping", FieldRoleMapperExpression.ofKeyValues("realm.name",
|
||||
"kerb1"), Collections.singletonList("superuser"), null, true));
|
||||
"kerb1"), Collections.singletonList("superuser"), Collections.emptyList(), null, true));
|
||||
final GetRoleMappingsResponse response = new GetRoleMappingsResponse(roleMappingsList);
|
||||
assertNotNull(response);
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(response, (original) -> {
|
||||
|
@ -101,15 +102,16 @@ public class GetRoleMappingsResponseTests extends ESTestCase {
|
|||
case 0:
|
||||
final List<ExpressionRoleMapping> roleMappingsList1 = new ArrayList<>();
|
||||
roleMappingsList1.add(new ExpressionRoleMapping("ldapmapping", FieldRoleMapperExpression.ofGroups(
|
||||
"cn=ipausers,cn=groups,cn=accounts,dc=ipademo,dc=local"), Collections.singletonList("monitoring"), null, false));
|
||||
"cn=ipausers,cn=groups,cn=accounts,dc=ipademo,dc=local"), Collections.singletonList("monitoring"), Collections.emptyList(),
|
||||
null, false));
|
||||
mutated = new GetRoleMappingsResponse(roleMappingsList1);
|
||||
break;
|
||||
case 1:
|
||||
final List<ExpressionRoleMapping> roleMappingsList2 = new ArrayList<>();
|
||||
ExpressionRoleMapping orginialRoleMapping = original.getMappings().get(0);
|
||||
roleMappingsList2.add(new ExpressionRoleMapping(orginialRoleMapping.getName(), FieldRoleMapperExpression.ofGroups(
|
||||
"cn=ipausers,cn=groups,cn=accounts,dc=ipademo,dc=local"),
|
||||
orginialRoleMapping.getRoles(), orginialRoleMapping.getMetadata(), !orginialRoleMapping.isEnabled()));
|
||||
ExpressionRoleMapping originalRoleMapping = original.getMappings().get(0);
|
||||
roleMappingsList2.add(new ExpressionRoleMapping(originalRoleMapping.getName(),
|
||||
FieldRoleMapperExpression.ofGroups("cn=ipausers,cn=groups,cn=accounts,dc=ipademo,dc=local"), originalRoleMapping.getRoles(),
|
||||
Collections.emptyList(), originalRoleMapping.getMetadata(), !originalRoleMapping.isEnabled()));
|
||||
mutated = new GetRoleMappingsResponse(roleMappingsList2);
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -29,12 +29,12 @@ import org.elasticsearch.test.ESTestCase;
|
|||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
||||
|
@ -49,7 +49,8 @@ public class PutRoleMappingRequestTests extends ESTestCase {
|
|||
metadata.put("k1", "v1");
|
||||
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
|
||||
|
||||
PutRoleMappingRequest putRoleMappingRequest = new PutRoleMappingRequest(name, enabled, roles, rules, metadata, refreshPolicy);
|
||||
PutRoleMappingRequest putRoleMappingRequest = new PutRoleMappingRequest(name, enabled, roles, Collections.emptyList(), rules,
|
||||
metadata, refreshPolicy);
|
||||
assertNotNull(putRoleMappingRequest);
|
||||
assertThat(putRoleMappingRequest.getName(), equalTo(name));
|
||||
assertThat(putRoleMappingRequest.isEnabled(), equalTo(enabled));
|
||||
|
@ -68,23 +69,39 @@ public class PutRoleMappingRequestTests extends ESTestCase {
|
|||
metadata.put("k1", "v1");
|
||||
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
|
||||
|
||||
final IllegalArgumentException ile = expectThrows(IllegalArgumentException.class, () -> new PutRoleMappingRequest(name, enabled,
|
||||
roles, rules, metadata, refreshPolicy));
|
||||
final IllegalArgumentException ile = expectThrows(IllegalArgumentException.class,
|
||||
() -> new PutRoleMappingRequest(name, enabled, roles, Collections.emptyList(), rules, metadata, refreshPolicy));
|
||||
assertThat(ile.getMessage(), equalTo("role-mapping name is missing"));
|
||||
}
|
||||
|
||||
public void testPutRoleMappingRequestThrowsExceptionForNullOrEmptyRoles() {
|
||||
public void testPutRoleMappingRequestThrowsExceptionForNullRoles() {
|
||||
final String name = randomAlphaOfLength(5);
|
||||
final boolean enabled = randomBoolean();
|
||||
final List<String> roles = randomBoolean() ? null : Collections.emptyList();
|
||||
final List<String> roles = null ;
|
||||
final List<TemplateRoleName> roleTemplates = Collections.emptyList();
|
||||
final RoleMapperExpression rules = FieldRoleMapperExpression.ofUsername("user");
|
||||
final Map<String, Object> metadata = new HashMap<>();
|
||||
metadata.put("k1", "v1");
|
||||
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
|
||||
|
||||
final IllegalArgumentException ile = expectThrows(IllegalArgumentException.class, () -> new PutRoleMappingRequest(name, enabled,
|
||||
roles, rules, metadata, refreshPolicy));
|
||||
assertThat(ile.getMessage(), equalTo("role-mapping roles are missing"));
|
||||
final RuntimeException ex = expectThrows(RuntimeException.class,
|
||||
() -> new PutRoleMappingRequest(name, enabled, roles, roleTemplates, rules, metadata, refreshPolicy));
|
||||
assertThat(ex.getMessage(), equalTo("role-mapping roles cannot be null"));
|
||||
}
|
||||
|
||||
public void testPutRoleMappingRequestThrowsExceptionForEmptyRoles() {
|
||||
final String name = randomAlphaOfLength(5);
|
||||
final boolean enabled = randomBoolean();
|
||||
final List<String> roles = Collections.emptyList();
|
||||
final List<TemplateRoleName> roleTemplates = Collections.emptyList();
|
||||
final RoleMapperExpression rules = FieldRoleMapperExpression.ofUsername("user");
|
||||
final Map<String, Object> metadata = new HashMap<>();
|
||||
metadata.put("k1", "v1");
|
||||
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
|
||||
|
||||
final RuntimeException ex = expectThrows(RuntimeException.class,
|
||||
() -> new PutRoleMappingRequest(name, enabled, roles, roleTemplates, rules, metadata, refreshPolicy));
|
||||
assertThat(ex.getMessage(), equalTo("in a role-mapping, one of roles or role_templates is required"));
|
||||
}
|
||||
|
||||
public void testPutRoleMappingRequestThrowsExceptionForNullRules() {
|
||||
|
@ -96,7 +113,8 @@ public class PutRoleMappingRequestTests extends ESTestCase {
|
|||
metadata.put("k1", "v1");
|
||||
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
|
||||
|
||||
expectThrows(NullPointerException.class, () -> new PutRoleMappingRequest(name, enabled, roles, rules, metadata, refreshPolicy));
|
||||
expectThrows(NullPointerException.class, () -> new PutRoleMappingRequest(name, enabled, roles, Collections.emptyList(), rules,
|
||||
metadata, refreshPolicy));
|
||||
}
|
||||
|
||||
public void testPutRoleMappingRequestToXContent() throws IOException {
|
||||
|
@ -108,7 +126,8 @@ public class PutRoleMappingRequestTests extends ESTestCase {
|
|||
metadata.put("k1", "v1");
|
||||
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
|
||||
|
||||
final PutRoleMappingRequest putRoleMappingRequest = new PutRoleMappingRequest(name, enabled, roles, rules, metadata, refreshPolicy);
|
||||
final PutRoleMappingRequest putRoleMappingRequest = new PutRoleMappingRequest(name, enabled, roles, Collections.emptyList(), rules,
|
||||
metadata, refreshPolicy);
|
||||
|
||||
final XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
putRoleMappingRequest.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
|
@ -117,6 +136,42 @@ public class PutRoleMappingRequestTests extends ESTestCase {
|
|||
"{"+
|
||||
"\"enabled\":" + enabled + "," +
|
||||
"\"roles\":[\"superuser\"]," +
|
||||
"\"role_templates\":[]," +
|
||||
"\"rules\":{" +
|
||||
"\"field\":{\"username\":[\"user\"]}" +
|
||||
"}," +
|
||||
"\"metadata\":{\"k1\":\"v1\"}" +
|
||||
"}";
|
||||
|
||||
assertThat(output, equalTo(expected));
|
||||
}
|
||||
|
||||
public void testPutRoleMappingRequestWithTemplateToXContent() throws IOException {
|
||||
final String name = randomAlphaOfLength(5);
|
||||
final boolean enabled = randomBoolean();
|
||||
final List<TemplateRoleName> templates = Arrays.asList(
|
||||
new TemplateRoleName(Collections.singletonMap("source" , "_realm_{{realm.name}}"), TemplateRoleName.Format.STRING),
|
||||
new TemplateRoleName(Collections.singletonMap("source" , "some_role"), TemplateRoleName.Format.STRING)
|
||||
);
|
||||
final RoleMapperExpression rules = FieldRoleMapperExpression.ofUsername("user");
|
||||
final Map<String, Object> metadata = new HashMap<>();
|
||||
metadata.put("k1", "v1");
|
||||
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
|
||||
|
||||
final PutRoleMappingRequest putRoleMappingRequest = new PutRoleMappingRequest(name, enabled, Collections.emptyList(), templates,
|
||||
rules, metadata, refreshPolicy);
|
||||
|
||||
final XContentBuilder builder = XContentFactory.jsonBuilder();
|
||||
putRoleMappingRequest.toXContent(builder, ToXContent.EMPTY_PARAMS);
|
||||
final String output = Strings.toString(builder);
|
||||
final String expected =
|
||||
"{"+
|
||||
"\"enabled\":" + enabled + "," +
|
||||
"\"roles\":[]," +
|
||||
"\"role_templates\":[" +
|
||||
"{\"template\":\"{\\\"source\\\":\\\"_realm_{{realm.name}}\\\"}\",\"format\":\"string\"}," +
|
||||
"{\"template\":\"{\\\"source\\\":\\\"some_role\\\"}\",\"format\":\"string\"}" +
|
||||
"]," +
|
||||
"\"rules\":{" +
|
||||
"\"field\":{\"username\":[\"user\"]}" +
|
||||
"}," +
|
||||
|
@ -129,48 +184,59 @@ public class PutRoleMappingRequestTests extends ESTestCase {
|
|||
public void testEqualsHashCode() {
|
||||
final String name = randomAlphaOfLength(5);
|
||||
final boolean enabled = randomBoolean();
|
||||
final List<String> roles = Collections.singletonList("superuser");
|
||||
final List<String> roles;
|
||||
final List<TemplateRoleName> templates;
|
||||
if (randomBoolean()) {
|
||||
roles = Arrays.asList(randomArray(1, 3, String[]::new, () -> randomAlphaOfLengthBetween(6, 12)));
|
||||
templates = Collections.emptyList();
|
||||
} else {
|
||||
roles = Collections.emptyList();
|
||||
templates = Arrays.asList(
|
||||
randomArray(1, 3, TemplateRoleName[]::new,
|
||||
() -> new TemplateRoleName(randomAlphaOfLengthBetween(12, 60), randomFrom(TemplateRoleName.Format.values()))
|
||||
));
|
||||
}
|
||||
final RoleMapperExpression rules = FieldRoleMapperExpression.ofUsername("user");
|
||||
final Map<String, Object> metadata = new HashMap<>();
|
||||
metadata.put("k1", "v1");
|
||||
final RefreshPolicy refreshPolicy = randomFrom(RefreshPolicy.values());
|
||||
|
||||
PutRoleMappingRequest putRoleMappingRequest = new PutRoleMappingRequest(name, enabled, roles, rules, metadata, refreshPolicy);
|
||||
PutRoleMappingRequest putRoleMappingRequest = new PutRoleMappingRequest(name, enabled, roles, templates, rules, metadata,
|
||||
refreshPolicy);
|
||||
assertNotNull(putRoleMappingRequest);
|
||||
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(putRoleMappingRequest, (original) -> {
|
||||
return new PutRoleMappingRequest(original.getName(), original.isEnabled(), original.getRoles(), original.getRules(), original
|
||||
.getMetadata(), original.getRefreshPolicy());
|
||||
});
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(putRoleMappingRequest, (original) -> {
|
||||
return new PutRoleMappingRequest(original.getName(), original.isEnabled(), original.getRoles(), original.getRules(), original
|
||||
.getMetadata(), original.getRefreshPolicy());
|
||||
return new PutRoleMappingRequest(original.getName(), original.isEnabled(), original.getRoles(), original.getRoleTemplates(),
|
||||
original.getRules(), original.getMetadata(), original.getRefreshPolicy());
|
||||
}, PutRoleMappingRequestTests::mutateTestItem);
|
||||
}
|
||||
|
||||
private static PutRoleMappingRequest mutateTestItem(PutRoleMappingRequest original) {
|
||||
switch (randomIntBetween(0, 4)) {
|
||||
switch (randomIntBetween(0, 5)) {
|
||||
case 0:
|
||||
return new PutRoleMappingRequest(randomAlphaOfLength(5), original.isEnabled(), original.getRoles(), original.getRules(),
|
||||
original.getMetadata(), original.getRefreshPolicy());
|
||||
return new PutRoleMappingRequest(randomAlphaOfLength(5), original.isEnabled(), original.getRoles(),
|
||||
original.getRoleTemplates(), original.getRules(), original.getMetadata(), original.getRefreshPolicy());
|
||||
case 1:
|
||||
return new PutRoleMappingRequest(original.getName(), !original.isEnabled(), original.getRoles(), original.getRules(),
|
||||
original.getMetadata(), original.getRefreshPolicy());
|
||||
return new PutRoleMappingRequest(original.getName(), !original.isEnabled(), original.getRoles(), original.getRoleTemplates(),
|
||||
original.getRules(), original.getMetadata(), original.getRefreshPolicy());
|
||||
case 2:
|
||||
return new PutRoleMappingRequest(original.getName(), original.isEnabled(), original.getRoles(),
|
||||
return new PutRoleMappingRequest(original.getName(), original.isEnabled(), original.getRoles(), original.getRoleTemplates(),
|
||||
FieldRoleMapperExpression.ofGroups("group"), original.getMetadata(), original.getRefreshPolicy());
|
||||
case 3:
|
||||
return new PutRoleMappingRequest(original.getName(), original.isEnabled(), original.getRoles(), original.getRules(),
|
||||
Collections.emptyMap(), original.getRefreshPolicy());
|
||||
return new PutRoleMappingRequest(original.getName(), original.isEnabled(), original.getRoles(), original.getRoleTemplates(),
|
||||
original.getRules(), Collections.emptyMap(), original.getRefreshPolicy());
|
||||
case 4:
|
||||
List<RefreshPolicy> values = Arrays.stream(RefreshPolicy.values())
|
||||
.filter(rp -> rp != original.getRefreshPolicy())
|
||||
.collect(Collectors.toList());
|
||||
return new PutRoleMappingRequest(original.getName(), original.isEnabled(), original.getRoles(), original.getRules(), original
|
||||
.getMetadata(), randomFrom(values));
|
||||
return new PutRoleMappingRequest(original.getName(), original.isEnabled(), original.getRoles(), original.getRoleTemplates(),
|
||||
original.getRules(), original.getMetadata(),
|
||||
randomValueOtherThan(original.getRefreshPolicy(), () -> randomFrom(RefreshPolicy.values())));
|
||||
case 5:
|
||||
List<String> roles = new ArrayList<>(original.getRoles());
|
||||
roles.add(randomAlphaOfLengthBetween(3, 5));
|
||||
return new PutRoleMappingRequest(original.getName(), original.isEnabled(), roles, Collections.emptyList(),
|
||||
original.getRules(), original.getMetadata(), original.getRefreshPolicy());
|
||||
|
||||
default:
|
||||
return new PutRoleMappingRequest(randomAlphaOfLength(5), original.isEnabled(), original.getRoles(), original.getRules(),
|
||||
original.getMetadata(), original.getRefreshPolicy());
|
||||
throw new IllegalStateException("Bad random value");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -50,15 +50,46 @@ mapping is performed.
|
|||
user. Within the `metadata` object, keys beginning with `_` are reserved for
|
||||
system usage.
|
||||
|
||||
`roles` (required)::
|
||||
(list) A list of roles that are granted to the users that match the role mapping
|
||||
rules.
|
||||
`roles`::
|
||||
(list of strings) A list of role names that are granted to the users that match
|
||||
the role mapping rules.
|
||||
_Exactly one of `roles` or `role_templates` must be specified_.
|
||||
|
||||
`role_templates`::
|
||||
(list of objects) A list of mustache templates that will be evaluated to
|
||||
determine the roles names that should granted to the users that match the role
|
||||
mapping rules.
|
||||
The format of these objects is defined below.
|
||||
_Exactly one of `roles` or `role_templates` must be specified_.
|
||||
|
||||
`rules` (required)::
|
||||
(object) The rules that determine which users should be matched by the mapping.
|
||||
A rule is a logical condition that is expressed by using a JSON DSL. See
|
||||
<<role-mapping-resources>>.
|
||||
|
||||
==== Role Templates
|
||||
|
||||
The most common use for role mappings is to create a mapping from a known value
|
||||
on the user to a fixed role name.
|
||||
For example, all users in the `cn=admin,dc=example,dc=com` LDAP group should be
|
||||
given the `superuser` role in {es}.
|
||||
The `roles` field is used for this purpose.
|
||||
|
||||
For more complex needs it is possible to use Mustache templates to dynamically
|
||||
determine the names of the roles that should be granted to the user.
|
||||
The `role_templates` field is used for this purpose.
|
||||
|
||||
All of the <<role-mapping-resources,user fields>> that are available in the
|
||||
role mapping `rules` are also available in the role templates. Thus it is possible
|
||||
to assign a user to a role that reflects their `username`, their `groups` or the
|
||||
name of the `realm` to which they authenticated.
|
||||
|
||||
By default a template is evaluated to produce a single string that is the name
|
||||
of the role which should be assigned to the user. If the `format` of the template
|
||||
is set to `"json"` then the template is expected to produce a JSON string, or an
|
||||
array of JSON strings for the role name(s).
|
||||
|
||||
The Examples section below demonstrates the use of templated role names.
|
||||
|
||||
==== Authorization
|
||||
|
||||
|
@ -117,12 +148,26 @@ POST /_security/role_mapping/mapping2
|
|||
--------------------------------------------------
|
||||
// CONSOLE
|
||||
|
||||
The following example matches users who authenticated against a specific realm:
|
||||
[source, js]
|
||||
------------------------------------------------------------
|
||||
POST /_security/role_mapping/mapping3
|
||||
{
|
||||
"roles": [ "ldap-user" ],
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"field" : { "realm.name" : "ldap1" }
|
||||
}
|
||||
}
|
||||
------------------------------------------------------------
|
||||
// CONSOLE
|
||||
|
||||
The following example matches any user where either the username is `esadmin`
|
||||
or the user is in the `cn=admin,dc=example,dc=com` group:
|
||||
|
||||
[source, js]
|
||||
------------------------------------------------------------
|
||||
POST /_security/role_mapping/mapping3
|
||||
POST /_security/role_mapping/mapping4
|
||||
{
|
||||
"roles": [ "superuser" ],
|
||||
"enabled": true,
|
||||
|
@ -144,25 +189,52 @@ POST /_security/role_mapping/mapping3
|
|||
------------------------------------------------------------
|
||||
// CONSOLE
|
||||
|
||||
The following example matches users who authenticated against a specific realm:
|
||||
The example above is useful when the group names in your identity management
|
||||
system (such as Active Directory, or a SAML Identity Provider) do not have a
|
||||
1-to-1 correspondence with the names of roles in {es}. The role mapping is the
|
||||
means by which you link a _group name_ with a _role name_.
|
||||
|
||||
However, in rare cases the names of your groups may be an exact match for the
|
||||
names of your {es} roles. This can be the case when your SAML Identity Provider
|
||||
includes its own "group mapping" feature and can be configured to release {es}
|
||||
role names in the user's SAML attributes.
|
||||
|
||||
In these cases it is possible to use a template that treats the group names as
|
||||
role names.
|
||||
|
||||
*Note*: This should only be done if you intend to define roles for all of the
|
||||
provided groups. Mapping a user to a large number of unnecessary or undefined
|
||||
roles is inefficient and can have a negative effect on system performance.
|
||||
If you only need to map a subset of the groups, then you should do this
|
||||
using explicit mappings.
|
||||
|
||||
[source, js]
|
||||
------------------------------------------------------------
|
||||
POST /_security/role_mapping/mapping4
|
||||
POST /_security/role_mapping/mapping5
|
||||
{
|
||||
"roles": [ "ldap-user" ],
|
||||
"enabled": true,
|
||||
"role_templates": [
|
||||
{
|
||||
"template": { "source": "{{#tojson}}groups{{/tojson}}" }, <1>
|
||||
"format" : "json" <2>
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"field" : { "realm.name" : "ldap1" }
|
||||
}
|
||||
"field" : { "realm.name" : "saml1" }
|
||||
},
|
||||
"enabled": true
|
||||
}
|
||||
------------------------------------------------------------
|
||||
// CONSOLE
|
||||
<1> The `tojson` mustache function is used to convert the list of
|
||||
group names into a valid JSON array.
|
||||
<2> Because the template produces a JSON array, the format must be
|
||||
set to `json`.
|
||||
|
||||
The following example matches users within a specific LDAP sub-tree:
|
||||
|
||||
[source, js]
|
||||
------------------------------------------------------------
|
||||
POST /_security/role_mapping/mapping5
|
||||
POST /_security/role_mapping/mapping6
|
||||
{
|
||||
"roles": [ "example-user" ],
|
||||
"enabled": true,
|
||||
|
@ -178,7 +250,7 @@ specific realm:
|
|||
|
||||
[source, js]
|
||||
------------------------------------------------------------
|
||||
POST /_security/role_mapping/mapping6
|
||||
POST /_security/role_mapping/mapping7
|
||||
{
|
||||
"roles": [ "ldap-example-user" ],
|
||||
"enabled": true,
|
||||
|
@ -203,7 +275,7 @@ following mapping matches any user where *all* of these conditions are met:
|
|||
|
||||
[source, js]
|
||||
------------------------------------------------------------
|
||||
POST /_security/role_mapping/mapping7
|
||||
POST /_security/role_mapping/mapping8
|
||||
{
|
||||
"roles": [ "superuser" ],
|
||||
"enabled": true,
|
||||
|
@ -240,3 +312,32 @@ POST /_security/role_mapping/mapping7
|
|||
}
|
||||
------------------------------------------------------------
|
||||
// CONSOLE
|
||||
|
||||
A templated role can be used to automatically map every user to their own
|
||||
custom role. The role itself can be defined through the
|
||||
<<security-api-put-role, Roles API>> or using a
|
||||
{stack-ov}/custom-roles-authorization.html#implementing-custom-roles-provider[custom roles provider].
|
||||
|
||||
In this example every user who authenticates using the "cloud-saml" realm
|
||||
will be automatically mapped to two roles - the `"saml_user"` role and a
|
||||
role that is their username prefixed with `_user_`.
|
||||
As an example, the user `nwong` would be assigned the `saml_user` and
|
||||
`_user_nwong` roles.
|
||||
|
||||
[source, js]
|
||||
------------------------------------------------------------
|
||||
POST /_security/role_mapping/mapping9
|
||||
{
|
||||
"rules": { "field": { "realm.name": "cloud-saml" } },
|
||||
"role_templates": [
|
||||
{ "template": { "source" : "saml_user" } }, <1>
|
||||
{ "template": { "source" : "_user_{{username}}" } }
|
||||
],
|
||||
"enabled": true
|
||||
}
|
||||
------------------------------------------------------------
|
||||
// CONSOLE
|
||||
<1> Because it is not possible to specify both `roles` and `role_templates` in
|
||||
the same role mapping, we can apply a "fixed name" role by using a template
|
||||
that has no substitutions.
|
||||
|
||||
|
|
|
@ -57,9 +57,9 @@ import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleFeatureSetUsage
|
|||
import org.elasticsearch.xpack.core.indexlifecycle.IndexLifecycleMetadata;
|
||||
import org.elasticsearch.xpack.core.indexlifecycle.LifecycleAction;
|
||||
import org.elasticsearch.xpack.core.indexlifecycle.LifecycleType;
|
||||
import org.elasticsearch.xpack.core.indexlifecycle.SetPriorityAction;
|
||||
import org.elasticsearch.xpack.core.indexlifecycle.ReadOnlyAction;
|
||||
import org.elasticsearch.xpack.core.indexlifecycle.RolloverAction;
|
||||
import org.elasticsearch.xpack.core.indexlifecycle.SetPriorityAction;
|
||||
import org.elasticsearch.xpack.core.indexlifecycle.ShrinkAction;
|
||||
import org.elasticsearch.xpack.core.indexlifecycle.TimeseriesLifecycleType;
|
||||
import org.elasticsearch.xpack.core.indexlifecycle.UnfollowAction;
|
||||
|
|
|
@ -5,12 +5,14 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.core.security.action.rolemapping;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.action.ActionRequest;
|
||||
import org.elasticsearch.action.ActionRequestValidationException;
|
||||
import org.elasticsearch.action.support.WriteRequest;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.TemplateRoleName;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExpressionParser;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.RoleMapperExpression;
|
||||
import org.elasticsearch.xpack.core.security.support.MetadataUtils;
|
||||
|
@ -35,6 +37,7 @@ public class PutRoleMappingRequest extends ActionRequest
|
|||
private String name = null;
|
||||
private boolean enabled = true;
|
||||
private List<String> roles = Collections.emptyList();
|
||||
private List<TemplateRoleName> roleTemplates = Collections.emptyList();
|
||||
private RoleMapperExpression rules = null;
|
||||
private Map<String, Object> metadata = Collections.emptyMap();
|
||||
private RefreshPolicy refreshPolicy = RefreshPolicy.IMMEDIATE;
|
||||
|
@ -46,20 +49,20 @@ public class PutRoleMappingRequest extends ActionRequest
|
|||
public ActionRequestValidationException validate() {
|
||||
ActionRequestValidationException validationException = null;
|
||||
if (name == null) {
|
||||
validationException = addValidationError("role-mapping name is missing",
|
||||
validationException);
|
||||
validationException = addValidationError("role-mapping name is missing", validationException);
|
||||
}
|
||||
if (roles.isEmpty()) {
|
||||
validationException = addValidationError("role-mapping roles are missing",
|
||||
validationException);
|
||||
if (roles.isEmpty() && roleTemplates.isEmpty()) {
|
||||
validationException = addValidationError("role-mapping roles or role-templates are missing", validationException);
|
||||
}
|
||||
if (roles.size() > 0 && roleTemplates.size() > 0) {
|
||||
validationException = addValidationError("role-mapping cannot have both roles and role-templates", validationException);
|
||||
}
|
||||
if (rules == null) {
|
||||
validationException = addValidationError("role-mapping rules are missing",
|
||||
validationException);
|
||||
validationException = addValidationError("role-mapping rules are missing", validationException);
|
||||
}
|
||||
if (MetadataUtils.containsReservedMetadata(metadata)) {
|
||||
validationException = addValidationError("metadata keys may not start with [" +
|
||||
MetadataUtils.RESERVED_PREFIX + "]", validationException);
|
||||
validationException = addValidationError("metadata keys may not start with [" + MetadataUtils.RESERVED_PREFIX + "]",
|
||||
validationException);
|
||||
}
|
||||
return validationException;
|
||||
}
|
||||
|
@ -84,10 +87,18 @@ public class PutRoleMappingRequest extends ActionRequest
|
|||
return Collections.unmodifiableList(roles);
|
||||
}
|
||||
|
||||
public List<TemplateRoleName> getRoleTemplates() {
|
||||
return Collections.unmodifiableList(roleTemplates);
|
||||
}
|
||||
|
||||
public void setRoles(List<String> roles) {
|
||||
this.roles = new ArrayList<>(roles);
|
||||
}
|
||||
|
||||
public void setRoleTemplates(List<TemplateRoleName> templates) {
|
||||
this.roleTemplates = new ArrayList<>(templates);
|
||||
}
|
||||
|
||||
public RoleMapperExpression getRules() {
|
||||
return rules;
|
||||
}
|
||||
|
@ -126,6 +137,9 @@ public class PutRoleMappingRequest extends ActionRequest
|
|||
this.name = in.readString();
|
||||
this.enabled = in.readBoolean();
|
||||
this.roles = in.readStringList();
|
||||
if (in.getVersion().onOrAfter(Version.V_7_1_0)) {
|
||||
this.roleTemplates = in.readList(TemplateRoleName::new);
|
||||
}
|
||||
this.rules = ExpressionParser.readExpression(in);
|
||||
this.metadata = in.readMap();
|
||||
this.refreshPolicy = RefreshPolicy.readFrom(in);
|
||||
|
@ -137,6 +151,9 @@ public class PutRoleMappingRequest extends ActionRequest
|
|||
out.writeString(name);
|
||||
out.writeBoolean(enabled);
|
||||
out.writeStringCollection(roles);
|
||||
if (out.getVersion().onOrAfter(Version.V_7_1_0)) {
|
||||
out.writeList(roleTemplates);
|
||||
}
|
||||
ExpressionParser.writeExpression(rules, out);
|
||||
out.writeMap(metadata);
|
||||
refreshPolicy.writeTo(out);
|
||||
|
@ -147,6 +164,7 @@ public class PutRoleMappingRequest extends ActionRequest
|
|||
name,
|
||||
rules,
|
||||
roles,
|
||||
roleTemplates,
|
||||
metadata,
|
||||
enabled
|
||||
);
|
||||
|
|
|
@ -5,18 +5,19 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.core.security.action.rolemapping;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.elasticsearch.action.ActionRequestBuilder;
|
||||
import org.elasticsearch.action.support.WriteRequestBuilder;
|
||||
import org.elasticsearch.client.ElasticsearchClient;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.TemplateRoleName;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.RoleMapperExpression;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Builder for requests to add/update a role-mapping to the native store
|
||||
*
|
||||
|
@ -38,6 +39,7 @@ public class PutRoleMappingRequestBuilder extends ActionRequestBuilder<PutRoleMa
|
|||
request.setName(name);
|
||||
request.setEnabled(mapping.isEnabled());
|
||||
request.setRoles(mapping.getRoles());
|
||||
request.setRoleTemplates(mapping.getRoleTemplates());
|
||||
request.setRules(mapping.getExpression());
|
||||
request.setMetadata(mapping.getMetadata());
|
||||
return this;
|
||||
|
@ -52,6 +54,10 @@ public class PutRoleMappingRequestBuilder extends ActionRequestBuilder<PutRoleMa
|
|||
request.setRoles(Arrays.asList(roles));
|
||||
return this;
|
||||
}
|
||||
public PutRoleMappingRequestBuilder roleTemplates(TemplateRoleName... templates) {
|
||||
request.setRoleTemplates(Arrays.asList(templates));
|
||||
return this;
|
||||
}
|
||||
|
||||
public PutRoleMappingRequestBuilder expression(RoleMapperExpression expression) {
|
||||
request.setRules(expression);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.core.security.authc.support.mapper;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
|
@ -15,20 +16,28 @@ import org.elasticsearch.common.io.stream.Writeable;
|
|||
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser.ValueType;
|
||||
import org.elasticsearch.common.xcontent.ToXContentObject;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExpressionModel;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExpressionParser;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.RoleMapperExpression;
|
||||
import org.elasticsearch.xpack.core.security.authz.RoleDescriptor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A representation of a single role-mapping for use in NativeRoleMappingStore.
|
||||
|
@ -50,27 +59,30 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
|
||||
static {
|
||||
PARSER.declareStringArray(Builder::roles, Fields.ROLES);
|
||||
PARSER.declareField(Builder::rules, ExpressionParser::parseObject, Fields.RULES, ObjectParser.ValueType.OBJECT);
|
||||
PARSER.declareField(Builder::metadata, XContentParser::map, Fields.METADATA, ObjectParser.ValueType.OBJECT);
|
||||
PARSER.declareObjectArray(Builder::roleTemplates, (parser, ctx) -> TemplateRoleName.parse(parser), Fields.ROLE_TEMPLATES);
|
||||
PARSER.declareField(Builder::rules, ExpressionParser::parseObject, Fields.RULES, ValueType.OBJECT);
|
||||
PARSER.declareField(Builder::metadata, XContentParser::map, Fields.METADATA, ValueType.OBJECT);
|
||||
PARSER.declareBoolean(Builder::enabled, Fields.ENABLED);
|
||||
BiConsumer<Builder, String> ignored = (b, v) -> {
|
||||
};
|
||||
// skip the doc_type and type fields in case we're parsing directly from the index
|
||||
PARSER.declareString(ignored, new ParseField(NativeRoleMappingStoreField.DOC_TYPE_FIELD));
|
||||
PARSER.declareString(ignored, new ParseField(UPGRADE_API_TYPE_FIELD));
|
||||
}
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final RoleMapperExpression expression;
|
||||
private final List<String> roles;
|
||||
private final List<TemplateRoleName> roleTemplates ;
|
||||
private final Map<String, Object> metadata;
|
||||
private final boolean enabled;
|
||||
|
||||
public ExpressionRoleMapping(String name, RoleMapperExpression expr, List<String> roles, Map<String, Object> metadata,
|
||||
boolean enabled) {
|
||||
public ExpressionRoleMapping(String name, RoleMapperExpression expr, List<String> roles, List<TemplateRoleName> templates,
|
||||
Map<String, Object> metadata, boolean enabled) {
|
||||
this.name = name;
|
||||
this.expression = expr;
|
||||
this.roles = roles;
|
||||
this.roles = roles == null ? Collections.emptyList() : roles;
|
||||
this.roleTemplates = templates == null ? Collections.emptyList() : templates;
|
||||
this.metadata = metadata;
|
||||
this.enabled = enabled;
|
||||
}
|
||||
|
@ -79,6 +91,11 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
this.name = in.readString();
|
||||
this.enabled = in.readBoolean();
|
||||
this.roles = in.readStringList();
|
||||
if (in.getVersion().onOrAfter(Version.V_7_1_0)) {
|
||||
this.roleTemplates = in.readList(TemplateRoleName::new);
|
||||
} else {
|
||||
this.roleTemplates = Collections.emptyList();
|
||||
}
|
||||
this.expression = ExpressionParser.readExpression(in);
|
||||
this.metadata = in.readMap();
|
||||
}
|
||||
|
@ -88,6 +105,9 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
out.writeString(name);
|
||||
out.writeBoolean(enabled);
|
||||
out.writeStringCollection(roles);
|
||||
if (out.getVersion().onOrAfter(Version.V_7_1_0)) {
|
||||
out.writeList(roleTemplates);
|
||||
}
|
||||
ExpressionParser.writeExpression(expression, out);
|
||||
out.writeMap(metadata);
|
||||
}
|
||||
|
@ -103,7 +123,7 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
/**
|
||||
* The expression that determines whether the roles in this mapping should be applied to any given user.
|
||||
* If the expression
|
||||
* {@link RoleMapperExpression#match(org.elasticsearch.xpack.security.authc.support.mapper.expressiondsl.ExpressionModel) matches} a
|
||||
* {@link RoleMapperExpression#match(ExpressionModel) matches} a
|
||||
* org.elasticsearch.xpack.security.authc.support.UserRoleMapper.UserData user, then the user should be assigned this mapping's
|
||||
* {@link #getRoles() roles}
|
||||
*/
|
||||
|
@ -119,6 +139,14 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
return Collections.unmodifiableList(roles);
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of {@link RoleDescriptor roles} (specified by a {@link TemplateRoleName template} that evaluates to one or more names)
|
||||
* that should be assigned to users that match the {@link #getExpression() expression} in this mapping.
|
||||
*/
|
||||
public List<TemplateRoleName> getRoleTemplates() {
|
||||
return Collections.unmodifiableList(roleTemplates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Meta-data for this mapping. This exists for external systems of user to track information about this mapping such as where it was
|
||||
* sourced from, when it was loaded, etc.
|
||||
|
@ -137,7 +165,30 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getClass().getSimpleName() + "<" + name + " ; " + roles + " = " + Strings.toString(expression) + ">";
|
||||
return getClass().getSimpleName() + "<" + name + " ; " + roles + "/" + roleTemplates + " = " + Strings.toString(expression) + ">";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final ExpressionRoleMapping that = (ExpressionRoleMapping) o;
|
||||
return this.enabled == that.enabled &&
|
||||
Objects.equals(this.name, that.name) &&
|
||||
Objects.equals(this.expression, that.expression) &&
|
||||
Objects.equals(this.roles, that.roles) &&
|
||||
Objects.equals(this.roleTemplates, that.roleTemplates) &&
|
||||
Objects.equals(this.metadata, that.metadata);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(name, expression, roles, roleTemplates, metadata, enabled);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,7 +208,7 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
*/
|
||||
public static ExpressionRoleMapping parse(String name, XContentParser parser) throws IOException {
|
||||
try {
|
||||
final Builder builder = PARSER.parse(parser, null);
|
||||
final Builder builder = PARSER.parse(parser, name);
|
||||
return builder.build(name);
|
||||
} catch (IllegalArgumentException | IllegalStateException e) {
|
||||
throw new ParsingException(parser.getTokenLocation(), e.getMessage(), e);
|
||||
|
@ -166,38 +217,55 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
|
||||
/**
|
||||
* Converts this {@link ExpressionRoleMapping} into <em>XContent</em> that is compatible with
|
||||
* the format handled by {@link #parse(String, XContentParser)}.
|
||||
* the format handled by {@link #parse(String, BytesReference, XContentType)}.
|
||||
*/
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return toXContent(builder, params, false);
|
||||
}
|
||||
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params, boolean includeDocType) throws IOException {
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params, boolean indexFormat) throws IOException {
|
||||
builder.startObject();
|
||||
builder.field(Fields.ENABLED.getPreferredName(), enabled);
|
||||
builder.startArray(Fields.ROLES.getPreferredName());
|
||||
for (String r : roles) {
|
||||
builder.value(r);
|
||||
if (roles.isEmpty() == false) {
|
||||
builder.startArray(Fields.ROLES.getPreferredName());
|
||||
for (String r : roles) {
|
||||
builder.value(r);
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
if (roleTemplates.isEmpty() == false) {
|
||||
builder.startArray(Fields.ROLE_TEMPLATES.getPreferredName());
|
||||
for (TemplateRoleName r : roleTemplates) {
|
||||
builder.value(r);
|
||||
}
|
||||
builder.endArray();
|
||||
}
|
||||
builder.endArray();
|
||||
builder.field(Fields.RULES.getPreferredName());
|
||||
expression.toXContent(builder, params);
|
||||
|
||||
builder.field(Fields.METADATA.getPreferredName(), metadata);
|
||||
|
||||
if (includeDocType) {
|
||||
if (indexFormat) {
|
||||
builder.field(NativeRoleMappingStoreField.DOC_TYPE_FIELD, NativeRoleMappingStoreField.DOC_TYPE_ROLE_MAPPING);
|
||||
}
|
||||
return builder.endObject();
|
||||
}
|
||||
|
||||
public Set<String> getRoleNames(ScriptService scriptService, ExpressionModel model) {
|
||||
return Stream.concat(this.roles.stream(),
|
||||
this.roleTemplates.stream()
|
||||
.flatMap(r -> r.getRoleNames(scriptService, model).stream())
|
||||
).collect(Collectors.toSet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to facilitate the use of {@link ObjectParser} (via {@link #PARSER}).
|
||||
*/
|
||||
private static class Builder {
|
||||
private RoleMapperExpression rules;
|
||||
private List<String> roles;
|
||||
private List<TemplateRoleName> roleTemplates;
|
||||
private Map<String, Object> metadata = Collections.emptyMap();
|
||||
private Boolean enabled;
|
||||
|
||||
|
@ -207,7 +275,12 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
}
|
||||
|
||||
Builder roles(List<String> roles) {
|
||||
this.roles = roles;
|
||||
this.roles = new ArrayList<>(roles);
|
||||
return this;
|
||||
}
|
||||
|
||||
Builder roleTemplates(List<TemplateRoleName> templates) {
|
||||
this.roleTemplates = new ArrayList<>(templates);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -222,7 +295,7 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
}
|
||||
|
||||
private ExpressionRoleMapping build(String name) {
|
||||
if (roles == null) {
|
||||
if (roles == null && roleTemplates == null) {
|
||||
throw missingField(name, Fields.ROLES);
|
||||
}
|
||||
if (rules == null) {
|
||||
|
@ -231,17 +304,17 @@ public class ExpressionRoleMapping implements ToXContentObject, Writeable {
|
|||
if (enabled == null) {
|
||||
throw missingField(name, Fields.ENABLED);
|
||||
}
|
||||
return new ExpressionRoleMapping(name, rules, roles, metadata, enabled);
|
||||
return new ExpressionRoleMapping(name, rules, roles, roleTemplates, metadata, enabled);
|
||||
}
|
||||
|
||||
private IllegalStateException missingField(String id, ParseField field) {
|
||||
return new IllegalStateException("failed to parse role-mapping [" + id + "]. missing field [" + field + "]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface Fields {
|
||||
ParseField ROLES = new ParseField("roles");
|
||||
ParseField ROLE_TEMPLATES = new ParseField("role_templates");
|
||||
ParseField ENABLED = new ParseField("enabled");
|
||||
ParseField RULES = new ParseField("rules");
|
||||
ParseField METADATA = new ParseField("metadata");
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.core.security.authc.support.mapper;
|
||||
|
||||
import org.elasticsearch.common.ParseField;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.io.stream.StreamOutput;
|
||||
import org.elasticsearch.common.io.stream.Writeable;
|
||||
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
|
||||
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
import org.elasticsearch.common.xcontent.ObjectParser;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||
import org.elasticsearch.common.xcontent.XContentParseException;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExpressionModel;
|
||||
import org.elasticsearch.xpack.core.security.support.MustacheTemplateEvaluator;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UncheckedIOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
|
||||
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
|
||||
|
||||
/**
|
||||
* Representation of a Mustache template for expressing one or more roles names in a {@link ExpressionRoleMapping}.
|
||||
*/
|
||||
public class TemplateRoleName implements ToXContent, Writeable {
|
||||
|
||||
private static final ConstructingObjectParser<TemplateRoleName, Void> PARSER = new ConstructingObjectParser<>(
|
||||
"role-mapping-template", false, arr -> new TemplateRoleName((BytesReference) arr[0], (Format) arr[1]));
|
||||
|
||||
static {
|
||||
PARSER.declareField(constructorArg(), TemplateRoleName::extractTemplate, Fields.TEMPLATE, ObjectParser.ValueType.OBJECT_OR_STRING);
|
||||
PARSER.declareField(optionalConstructorArg(), Format::fromXContent, Fields.FORMAT, ObjectParser.ValueType.STRING);
|
||||
}
|
||||
|
||||
private final BytesReference template;
|
||||
private final Format format;
|
||||
|
||||
public TemplateRoleName(BytesReference template, Format format) {
|
||||
this.template = template;
|
||||
this.format = format == null ? Format.STRING : format;
|
||||
}
|
||||
|
||||
public TemplateRoleName(StreamInput in) throws IOException {
|
||||
this.template = in.readBytesReference();
|
||||
this.format = in.readEnum(Format.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(StreamOutput out) throws IOException {
|
||||
out.writeBytesReference(template);
|
||||
out.writeEnum(format);
|
||||
}
|
||||
|
||||
public BytesReference getTemplate() {
|
||||
return template;
|
||||
}
|
||||
|
||||
public Format getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public List<String> getRoleNames(ScriptService scriptService, ExpressionModel model) {
|
||||
try {
|
||||
final String evaluation = parseTemplate(scriptService, model.asMap());
|
||||
switch (format) {
|
||||
case STRING:
|
||||
return Collections.singletonList(evaluation);
|
||||
case JSON:
|
||||
return convertJsonToList(evaluation);
|
||||
default:
|
||||
throw new IllegalStateException("Unsupported format [" + format + "]");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> convertJsonToList(String evaluation) throws IOException {
|
||||
final XContentParser parser = XContentFactory.xContent(XContentType.JSON).createParser(NamedXContentRegistry.EMPTY,
|
||||
LoggingDeprecationHandler.INSTANCE, evaluation);
|
||||
XContentParser.Token token = parser.currentToken();
|
||||
if (token == null) {
|
||||
token = parser.nextToken();
|
||||
}
|
||||
if (token == XContentParser.Token.VALUE_STRING) {
|
||||
return Collections.singletonList(parser.text());
|
||||
} else if (token == XContentParser.Token.START_ARRAY) {
|
||||
return parser.list().stream()
|
||||
.filter(Objects::nonNull)
|
||||
.map(o -> {
|
||||
if (o instanceof String) {
|
||||
return (String) o;
|
||||
} else {
|
||||
throw new XContentParseException(
|
||||
"Roles array may only contain strings but found [" + o.getClass().getName() + "] [" + o + "]");
|
||||
}
|
||||
}).collect(Collectors.toList());
|
||||
} else {
|
||||
throw new XContentParseException(
|
||||
"Roles template must generate a string or an array of strings, but found [" + token + "]");
|
||||
}
|
||||
}
|
||||
|
||||
private String parseTemplate(ScriptService scriptService, Map<String, Object> parameters) throws IOException {
|
||||
final XContentParser parser = XContentHelper.createParser(
|
||||
NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, template, XContentType.JSON);
|
||||
return MustacheTemplateEvaluator.evaluate(scriptService, parser, parameters);
|
||||
}
|
||||
|
||||
private static BytesReference extractTemplate(XContentParser parser, Void ignore) throws IOException {
|
||||
if (parser.currentToken() == XContentParser.Token.VALUE_STRING) {
|
||||
return new BytesArray(parser.text());
|
||||
} else {
|
||||
XContentBuilder builder = JsonXContent.contentBuilder();
|
||||
builder.generator().copyCurrentStructure(parser);
|
||||
return BytesReference.bytes(builder);
|
||||
}
|
||||
}
|
||||
|
||||
static TemplateRoleName parse(XContentParser parser) throws IOException {
|
||||
return PARSER.parse(parser, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "template-" + format + "{" + template.utf8ToString() + "}";
|
||||
}
|
||||
|
||||
@Override
|
||||
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
|
||||
return builder.startObject()
|
||||
.field(Fields.TEMPLATE.getPreferredName(), template.utf8ToString())
|
||||
.field(Fields.FORMAT.getPreferredName(), format.formatName())
|
||||
.endObject();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFragment() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final TemplateRoleName that = (TemplateRoleName) o;
|
||||
return Objects.equals(this.template, that.template) &&
|
||||
this.format == that.format;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(template, format);
|
||||
}
|
||||
|
||||
private interface Fields {
|
||||
ParseField TEMPLATE = new ParseField("template");
|
||||
ParseField FORMAT = new ParseField("format");
|
||||
}
|
||||
|
||||
public enum Format {
|
||||
JSON, STRING;
|
||||
|
||||
private static Format fromXContent(XContentParser parser) throws IOException {
|
||||
final XContentParser.Token token = parser.currentToken();
|
||||
if (token != XContentParser.Token.VALUE_STRING) {
|
||||
throw new XContentParseException(parser.getTokenLocation(),
|
||||
"Expected [" + XContentParser.Token.VALUE_STRING + "] but found [" + token + "]");
|
||||
}
|
||||
final String text = parser.text();
|
||||
try {
|
||||
return Format.valueOf(text.toUpperCase(Locale.ROOT));
|
||||
} catch (IllegalArgumentException e) {
|
||||
String valueNames = Stream.of(values()).map(Format::formatName).collect(Collectors.joining(","));
|
||||
throw new XContentParseException(parser.getTokenLocation(),
|
||||
"Invalid format [" + text + "] expected one of [" + valueNames + "]");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public String formatName() {
|
||||
return name().toLowerCase(Locale.ROOT);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,9 +6,9 @@
|
|||
package org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl;
|
||||
|
||||
import org.elasticsearch.common.Numbers;
|
||||
import org.elasticsearch.common.collect.Tuple;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -22,10 +22,13 @@ import java.util.function.Predicate;
|
|||
public class ExpressionModel {
|
||||
|
||||
public static final Predicate<FieldExpression.FieldValue> NULL_PREDICATE = field -> field.getValue() == null;
|
||||
private Map<String, Tuple<Object, Predicate<FieldExpression.FieldValue>>> fields;
|
||||
|
||||
private final Map<String, Object> fieldValues;
|
||||
private final Map<String, Predicate<FieldExpression.FieldValue>> fieldPredicates;
|
||||
|
||||
public ExpressionModel() {
|
||||
this.fields = new HashMap<>();
|
||||
this.fieldValues = new HashMap<>();
|
||||
this.fieldPredicates = new HashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,7 +44,8 @@ public class ExpressionModel {
|
|||
* Defines a field using a supplied predicate.
|
||||
*/
|
||||
public ExpressionModel defineField(String name, Object value, Predicate<FieldExpression.FieldValue> predicate) {
|
||||
this.fields.put(name, new Tuple<>(value, predicate));
|
||||
this.fieldValues.put(name, value);
|
||||
this.fieldPredicates.put(name, predicate);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -49,13 +53,7 @@ public class ExpressionModel {
|
|||
* Returns {@code true} if the named field, matches <em>any</em> of the provided values.
|
||||
*/
|
||||
public boolean test(String field, List<FieldExpression.FieldValue> values) {
|
||||
final Tuple<Object, Predicate<FieldExpression.FieldValue>> tuple = this.fields.get(field);
|
||||
final Predicate<FieldExpression.FieldValue> predicate;
|
||||
if (tuple == null) {
|
||||
predicate = NULL_PREDICATE;
|
||||
} else {
|
||||
predicate = tuple.v2();
|
||||
}
|
||||
final Predicate<FieldExpression.FieldValue> predicate = this.fieldPredicates.getOrDefault(field, NULL_PREDICATE);
|
||||
return values.stream().anyMatch(predicate);
|
||||
}
|
||||
|
||||
|
@ -103,4 +101,12 @@ public class ExpressionModel {
|
|||
return Numbers.toLongExact(left) == Numbers.toLongExact(right);
|
||||
}
|
||||
|
||||
public Map<String, Object> asMap() {
|
||||
return Collections.unmodifiableMap(fieldValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return fieldValues.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import org.elasticsearch.xpack.core.security.support.Automatons;
|
|||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* An expression that evaluates to <code>true</code> if a field (map element) matches
|
||||
|
@ -151,6 +152,22 @@ public final class FieldExpression implements RoleMapperExpression {
|
|||
return builder.value(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final FieldValue that = (FieldValue) o;
|
||||
return Objects.equals(this.value, that.value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -11,10 +11,8 @@ import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
|
|||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.script.TemplateScript;
|
||||
import org.elasticsearch.xpack.core.security.support.MustacheTemplateEvaluator;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -66,27 +64,19 @@ public final class SecurityQueryTemplateEvaluator {
|
|||
if (token != XContentParser.Token.START_OBJECT) {
|
||||
throw new ElasticsearchParseException("Unexpected token [" + token + "]");
|
||||
}
|
||||
Script script = Script.parse(parser);
|
||||
// Add the user details to the params
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
if (script.getParams() != null) {
|
||||
params.putAll(script.getParams());
|
||||
}
|
||||
Map<String, Object> userModel = new HashMap<>();
|
||||
userModel.put("username", user.principal());
|
||||
userModel.put("full_name", user.fullName());
|
||||
userModel.put("email", user.email());
|
||||
userModel.put("roles", Arrays.asList(user.roles()));
|
||||
userModel.put("metadata", Collections.unmodifiableMap(user.metadata()));
|
||||
params.put("_user", userModel);
|
||||
// Always enforce mustache script lang:
|
||||
script = new Script(script.getType(), script.getType() == ScriptType.STORED ? null : "mustache", script.getIdOrCode(),
|
||||
script.getOptions(), params);
|
||||
TemplateScript compiledTemplate = scriptService.compile(script, TemplateScript.CONTEXT).newInstance(script.getParams());
|
||||
return compiledTemplate.execute();
|
||||
Map<String, Object> extraParams = Collections.singletonMap("_user", userModel);
|
||||
|
||||
return MustacheTemplateEvaluator.evaluate(scriptService, parser, extraParams);
|
||||
} else {
|
||||
return querySource;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.core.security.support;
|
||||
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.script.Script;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.script.ScriptType;
|
||||
import org.elasticsearch.script.TemplateScript;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Utility class for evaluating Mustache templates at runtime.
|
||||
*/
|
||||
public final class MustacheTemplateEvaluator {
|
||||
|
||||
private MustacheTemplateEvaluator() {
|
||||
throw new UnsupportedOperationException("Cannot construct " + MustacheTemplateEvaluator.class);
|
||||
}
|
||||
|
||||
public static String evaluate(ScriptService scriptService, XContentParser parser, Map<String, Object> extraParams) throws IOException {
|
||||
Script script = Script.parse(parser);
|
||||
// Add the user details to the params
|
||||
Map<String, Object> params = new HashMap<>();
|
||||
if (script.getParams() != null) {
|
||||
params.putAll(script.getParams());
|
||||
}
|
||||
extraParams.forEach(params::put);
|
||||
// Always enforce mustache script lang:
|
||||
script = new Script(script.getType(), script.getType() == ScriptType.STORED ? null : "mustache", script.getIdOrCode(),
|
||||
script.getOptions(), params);
|
||||
TemplateScript compiledTemplate = scriptService.compile(script, TemplateScript.CONTEXT).newInstance(script.getParams());
|
||||
return compiledTemplate.execute();
|
||||
}
|
||||
}
|
|
@ -45,6 +45,16 @@
|
|||
"roles" : {
|
||||
"type" : "keyword"
|
||||
},
|
||||
"role_templates" : {
|
||||
"properties": {
|
||||
"template" : {
|
||||
"type": "text"
|
||||
},
|
||||
"format" : {
|
||||
"type": "keyword"
|
||||
}
|
||||
}
|
||||
},
|
||||
"password" : {
|
||||
"type" : "keyword",
|
||||
"index" : false,
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
package org.elasticsearch.xpack.core.security.authc.support.mapper;
|
||||
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.xcontent.DeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.script.ScriptModule;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.script.mustache.MustacheScriptEngine;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.EqualsHashCodeTestUtils;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.TemplateRoleName.Format;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.ExpressionModel;
|
||||
import org.hamcrest.Matchers;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
public class TemplateRoleNameTests extends ESTestCase {
|
||||
|
||||
public void testParseRoles() throws Exception {
|
||||
final TemplateRoleName role1 = parse("{ \"template\": { \"source\": \"_user_{{username}}\" } }");
|
||||
assertThat(role1, Matchers.instanceOf(TemplateRoleName.class));
|
||||
assertThat(role1.getTemplate().utf8ToString(), equalTo("{\"source\":\"_user_{{username}}\"}"));
|
||||
assertThat(role1.getFormat(), equalTo(Format.STRING));
|
||||
|
||||
final TemplateRoleName role2 = parse(
|
||||
"{ \"template\": \"{\\\"source\\\":\\\"{{#tojson}}groups{{/tojson}}\\\"}\", \"format\":\"json\" }");
|
||||
assertThat(role2, Matchers.instanceOf(TemplateRoleName.class));
|
||||
assertThat(role2.getTemplate().utf8ToString(),
|
||||
equalTo("{\"source\":\"{{#tojson}}groups{{/tojson}}\"}"));
|
||||
assertThat(role2.getFormat(), equalTo(Format.JSON));
|
||||
}
|
||||
|
||||
public void testToXContent() throws Exception {
|
||||
final String json = "{" +
|
||||
"\"template\":\"{\\\"source\\\":\\\"" + randomAlphaOfLengthBetween(8, 24) + "\\\"}\"," +
|
||||
"\"format\":\"" + randomFrom(Format.values()).formatName() + "\"" +
|
||||
"}";
|
||||
assertThat(Strings.toString(parse(json)), equalTo(json));
|
||||
}
|
||||
|
||||
public void testSerializeTemplate() throws Exception {
|
||||
trySerialize(new TemplateRoleName(new BytesArray(randomAlphaOfLengthBetween(12, 60)), randomFrom(Format.values())));
|
||||
}
|
||||
|
||||
public void testEqualsAndHashCode() throws Exception {
|
||||
tryEquals(new TemplateRoleName(new BytesArray(randomAlphaOfLengthBetween(12, 60)), randomFrom(Format.values())));
|
||||
}
|
||||
|
||||
public void testEvaluateRoles() throws Exception {
|
||||
final ScriptService scriptService = new ScriptService(Settings.EMPTY,
|
||||
Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS);
|
||||
final ExpressionModel model = new ExpressionModel();
|
||||
model.defineField("username", "hulk");
|
||||
model.defineField("groups", Arrays.asList("avengers", "defenders", "panthenon"));
|
||||
|
||||
final TemplateRoleName plainString = new TemplateRoleName(new BytesArray("{ \"source\":\"heroes\" }"), Format.STRING);
|
||||
assertThat(plainString.getRoleNames(scriptService, model), contains("heroes"));
|
||||
|
||||
final TemplateRoleName user = new TemplateRoleName(new BytesArray("{ \"source\":\"_user_{{username}}\" }"), Format.STRING);
|
||||
assertThat(user.getRoleNames(scriptService, model), contains("_user_hulk"));
|
||||
|
||||
final TemplateRoleName groups = new TemplateRoleName(new BytesArray("{ \"source\":\"{{#tojson}}groups{{/tojson}}\" }"),
|
||||
Format.JSON);
|
||||
assertThat(groups.getRoleNames(scriptService, model), contains("avengers", "defenders", "panthenon"));
|
||||
}
|
||||
|
||||
private TemplateRoleName parse(String json) throws IOException {
|
||||
final XContentParser parser = XContentType.JSON.xContent()
|
||||
.createParser(NamedXContentRegistry.EMPTY, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
|
||||
final TemplateRoleName role = TemplateRoleName.parse(parser);
|
||||
assertThat(role, notNullValue());
|
||||
return role;
|
||||
}
|
||||
|
||||
public void trySerialize(TemplateRoleName original) throws Exception {
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
original.writeTo(output);
|
||||
|
||||
final StreamInput rawInput = ByteBufferStreamInput.wrap(BytesReference.toBytes(output.bytes()));
|
||||
final TemplateRoleName serialized = new TemplateRoleName(rawInput);
|
||||
assertEquals(original, serialized);
|
||||
}
|
||||
|
||||
public void tryEquals(TemplateRoleName original) {
|
||||
final EqualsHashCodeTestUtils.CopyFunction<TemplateRoleName> copy =
|
||||
rmt -> new TemplateRoleName(rmt.getTemplate(), rmt.getFormat());
|
||||
final EqualsHashCodeTestUtils.MutateFunction<TemplateRoleName> mutate = rmt -> {
|
||||
if (randomBoolean()) {
|
||||
return new TemplateRoleName(rmt.getTemplate(),
|
||||
randomValueOtherThan(rmt.getFormat(), () -> randomFrom(Format.values())));
|
||||
} else {
|
||||
final String templateStr = rmt.getTemplate().utf8ToString();
|
||||
return new TemplateRoleName(new BytesArray(templateStr.substring(randomIntBetween(1, templateStr.length() / 2))),
|
||||
rmt.getFormat());
|
||||
}
|
||||
};
|
||||
EqualsHashCodeTestUtils.checkEqualsAndHashCode(original, copy, mutate);
|
||||
}
|
||||
}
|
|
@ -370,7 +370,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
|||
NamedXContentRegistry xContentRegistry, Environment environment,
|
||||
NodeEnvironment nodeEnvironment, NamedWriteableRegistry namedWriteableRegistry) {
|
||||
try {
|
||||
return createComponents(client, threadPool, clusterService, resourceWatcherService);
|
||||
return createComponents(client, threadPool, clusterService, resourceWatcherService, scriptService);
|
||||
} catch (final Exception e) {
|
||||
throw new IllegalStateException("security initialization failed", e);
|
||||
}
|
||||
|
@ -378,7 +378,7 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
|||
|
||||
// pkg private for testing - tests want to pass in their set of extensions hence we are not using the extension service directly
|
||||
Collection<Object> createComponents(Client client, ThreadPool threadPool, ClusterService clusterService,
|
||||
ResourceWatcherService resourceWatcherService) throws Exception {
|
||||
ResourceWatcherService resourceWatcherService, ScriptService scriptService) throws Exception {
|
||||
if (enabled == false) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
@ -404,7 +404,8 @@ public class Security extends Plugin implements ActionPlugin, IngestPlugin, Netw
|
|||
|
||||
// realms construction
|
||||
final NativeUsersStore nativeUsersStore = new NativeUsersStore(settings, client, securityIndex.get());
|
||||
final NativeRoleMappingStore nativeRoleMappingStore = new NativeRoleMappingStore(settings, client, securityIndex.get());
|
||||
final NativeRoleMappingStore nativeRoleMappingStore = new NativeRoleMappingStore(settings, client, securityIndex.get(),
|
||||
scriptService);
|
||||
final AnonymousUser anonymousUser = new AnonymousUser(settings);
|
||||
final ReservedRealm reservedRealm = new ReservedRealm(env, settings, nativeUsersStore,
|
||||
anonymousUser, securityIndex.get(), threadPool);
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
|
|||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.index.query.QueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.xpack.core.security.ScrollHelper;
|
||||
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheAction;
|
||||
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheResponse;
|
||||
|
@ -51,7 +52,6 @@ import java.util.Set;
|
|||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.elasticsearch.action.DocWriteResponse.Result.CREATED;
|
||||
import static org.elasticsearch.action.DocWriteResponse.Result.DELETED;
|
||||
|
@ -99,12 +99,14 @@ public class NativeRoleMappingStore implements UserRoleMapper {
|
|||
private final Settings settings;
|
||||
private final Client client;
|
||||
private final SecurityIndexManager securityIndex;
|
||||
private final ScriptService scriptService;
|
||||
private final List<String> realmsToRefresh = new CopyOnWriteArrayList<>();
|
||||
|
||||
public NativeRoleMappingStore(Settings settings, Client client, SecurityIndexManager securityIndex) {
|
||||
public NativeRoleMappingStore(Settings settings, Client client, SecurityIndexManager securityIndex, ScriptService scriptService) {
|
||||
this.settings = settings;
|
||||
this.client = client;
|
||||
this.securityIndex = securityIndex;
|
||||
this.scriptService = scriptService;
|
||||
}
|
||||
|
||||
private String getNameFromId(String id) {
|
||||
|
@ -120,7 +122,7 @@ public class NativeRoleMappingStore implements UserRoleMapper {
|
|||
* Loads all mappings from the index.
|
||||
* <em>package private</em> for unit testing
|
||||
*/
|
||||
void loadMappings(ActionListener<List<ExpressionRoleMapping>> listener) {
|
||||
protected void loadMappings(ActionListener<List<ExpressionRoleMapping>> listener) {
|
||||
if (securityIndex.isIndexUpToDate() == false) {
|
||||
listener.onFailure(new IllegalStateException(
|
||||
"Security index is not on the current version - the native realm will not be operational until " +
|
||||
|
@ -149,7 +151,7 @@ public class NativeRoleMappingStore implements UserRoleMapper {
|
|||
}
|
||||
}
|
||||
|
||||
private ExpressionRoleMapping buildMapping(String id, BytesReference source) {
|
||||
protected ExpressionRoleMapping buildMapping(String id, BytesReference source) {
|
||||
try (InputStream stream = source.streamInput();
|
||||
XContentParser parser = XContentType.JSON.xContent()
|
||||
.createParser(NamedXContentRegistry.EMPTY, LoggingDeprecationHandler.INSTANCE, stream)) {
|
||||
|
@ -349,17 +351,16 @@ public class NativeRoleMappingStore implements UserRoleMapper {
|
|||
getRoleMappings(null, ActionListener.wrap(
|
||||
mappings -> {
|
||||
final ExpressionModel model = user.asModel();
|
||||
Stream<ExpressionRoleMapping> stream = mappings.stream()
|
||||
.filter(ExpressionRoleMapping::isEnabled)
|
||||
.filter(m -> m.getExpression().match(model));
|
||||
if (logger.isTraceEnabled()) {
|
||||
stream = stream.map(m -> {
|
||||
logger.trace("User [{}] matches role-mapping [{}] with roles [{}]", user.getUsername(), m.getName(),
|
||||
m.getRoles());
|
||||
return m;
|
||||
});
|
||||
}
|
||||
final Set<String> roles = stream.flatMap(m -> m.getRoles().stream()).collect(Collectors.toSet());
|
||||
final Set<String> roles = mappings.stream()
|
||||
.filter(ExpressionRoleMapping::isEnabled)
|
||||
.filter(m -> m.getExpression().match(model))
|
||||
.flatMap(m -> {
|
||||
final Set<String> roleNames = m.getRoleNames(scriptService, model);
|
||||
logger.trace("Applying role-mapping [{}] to user-model [{}] produced role-names [{}]",
|
||||
m.getName(), model, roleNames);
|
||||
return roleNames.stream();
|
||||
})
|
||||
.collect(Collectors.toSet());
|
||||
logger.debug("Mapping user [{}] to roles [{}]", user, roles);
|
||||
listener.onResponse(roles);
|
||||
}, listener::onFailure
|
||||
|
|
|
@ -27,6 +27,7 @@ import org.elasticsearch.license.License;
|
|||
import org.elasticsearch.license.TestUtils;
|
||||
import org.elasticsearch.license.XPackLicenseState;
|
||||
import org.elasticsearch.plugins.MapperPlugin;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.VersionUtils;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
|
@ -130,7 +131,7 @@ public class SecurityTests extends ESTestCase {
|
|||
Client client = mock(Client.class);
|
||||
when(client.threadPool()).thenReturn(threadPool);
|
||||
when(client.settings()).thenReturn(settings);
|
||||
return security.createComponents(client, threadPool, clusterService, mock(ResourceWatcherService.class));
|
||||
return security.createComponents(client, threadPool, clusterService, mock(ResourceWatcherService.class), mock(ScriptService.class));
|
||||
}
|
||||
|
||||
private static <T> T findComponent(Class<T> type, Collection<Object> components) {
|
||||
|
|
|
@ -25,9 +25,10 @@ import java.util.Collections;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.iterableWithSize;
|
||||
import static org.mockito.Matchers.any;
|
||||
import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
@ -72,7 +73,8 @@ public class TransportPutRoleMappingActionTests extends ESTestCase {
|
|||
assertThat(mapping.getExpression(), is(expression));
|
||||
assertThat(mapping.isEnabled(), equalTo(true));
|
||||
assertThat(mapping.getName(), equalTo("anarchy"));
|
||||
assertThat(mapping.getRoles(), containsInAnyOrder("superuser"));
|
||||
assertThat(mapping.getRoles(), iterableWithSize(1));
|
||||
assertThat(mapping.getRoles(), contains("superuser"));
|
||||
assertThat(mapping.getMetadata().size(), equalTo(1));
|
||||
assertThat(mapping.getMetadata().get("dumb"), equalTo(true));
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ import org.elasticsearch.common.util.concurrent.ThreadContext;
|
|||
import org.elasticsearch.common.util.set.Sets;
|
||||
import org.elasticsearch.env.TestEnvironment;
|
||||
import org.elasticsearch.license.XPackLicenseState;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.TestThreadPool;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
|
@ -146,7 +147,8 @@ public abstract class KerberosRealmTestCase extends ESTestCase {
|
|||
when(mockClient.threadPool()).thenReturn(threadPool);
|
||||
when(mockClient.settings()).thenReturn(settings);
|
||||
|
||||
final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, mockClient, mock(SecurityIndexManager.class));
|
||||
final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, mockClient, mock(SecurityIndexManager.class),
|
||||
mock(ScriptService.class));
|
||||
final NativeRoleMappingStore roleMapper = spy(store);
|
||||
|
||||
doAnswer(invocation -> {
|
||||
|
|
|
@ -8,6 +8,8 @@ package org.elasticsearch.xpack.security.authc.ldap;
|
|||
import com.unboundid.ldap.sdk.LDAPURL;
|
||||
import org.elasticsearch.action.ActionListener;
|
||||
import org.elasticsearch.action.support.PlainActionFuture;
|
||||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.settings.MockSecureSettings;
|
||||
import org.elasticsearch.common.settings.SecureSettings;
|
||||
import org.elasticsearch.common.settings.SecureString;
|
||||
|
@ -17,6 +19,9 @@ import org.elasticsearch.common.util.concurrent.ThreadContext;
|
|||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.env.TestEnvironment;
|
||||
import org.elasticsearch.license.XPackLicenseState;
|
||||
import org.elasticsearch.script.ScriptModule;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.script.mustache.MustacheScriptEngine;
|
||||
import org.elasticsearch.threadpool.TestThreadPool;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.watcher.ResourceWatcherService;
|
||||
|
@ -29,11 +34,14 @@ import org.elasticsearch.xpack.core.security.authc.ldap.LdapSessionFactorySettin
|
|||
import org.elasticsearch.xpack.core.security.authc.ldap.LdapUserSearchSessionFactorySettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.ldap.PoolingSessionFactorySettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.ldap.SearchGroupsResolverSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapMetaDataResolverSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.ldap.support.LdapSearchScope;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.CachingUsernamePasswordRealmSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.DelegatedAuthorizationSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.DnRoleMapperSettings;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.TemplateRoleName;
|
||||
import org.elasticsearch.xpack.core.security.user.User;
|
||||
import org.elasticsearch.xpack.core.ssl.SSLConfigurationSettings;
|
||||
import org.elasticsearch.xpack.core.ssl.SSLService;
|
||||
|
@ -42,6 +50,8 @@ import org.elasticsearch.xpack.security.authc.ldap.support.LdapTestCase;
|
|||
import org.elasticsearch.xpack.security.authc.ldap.support.SessionFactory;
|
||||
import org.elasticsearch.xpack.security.authc.support.DnRoleMapper;
|
||||
import org.elasticsearch.xpack.security.authc.support.MockLookupRealm;
|
||||
import org.elasticsearch.xpack.security.authc.support.mapper.NativeRoleMappingStore;
|
||||
import org.elasticsearch.xpack.security.support.SecurityIndexManager;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
|
||||
|
@ -54,6 +64,7 @@ import java.util.function.Function;
|
|||
import static org.elasticsearch.xpack.core.security.authc.RealmSettings.getFullSettingKey;
|
||||
import static org.elasticsearch.xpack.core.security.authc.ldap.support.SessionFactorySettings.URLS_SETTING;
|
||||
import static org.hamcrest.Matchers.arrayContaining;
|
||||
import static org.hamcrest.Matchers.arrayContainingInAnyOrder;
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
|
@ -394,6 +405,75 @@ public class LdapRealmTests extends LdapTestCase {
|
|||
assertThat(user.roles(), arrayContaining("avenger"));
|
||||
}
|
||||
|
||||
/**
|
||||
* This tests template role mappings (see
|
||||
* {@link TemplateRoleName}) with an LDAP realm, using a additional
|
||||
* metadata field (see {@link LdapMetaDataResolverSettings#ADDITIONAL_META_DATA_SETTING}).
|
||||
*/
|
||||
public void testLdapRealmWithTemplatedRoleMapping() throws Exception {
|
||||
String groupSearchBase = "o=sevenSeas";
|
||||
String userTemplate = VALID_USER_TEMPLATE;
|
||||
Settings settings = Settings.builder()
|
||||
.put(defaultGlobalSettings)
|
||||
.put(buildLdapSettings(ldapUrls(), userTemplate, groupSearchBase, LdapSearchScope.SUB_TREE))
|
||||
.put(getFullSettingKey(REALM_IDENTIFIER.getName(), LdapMetaDataResolverSettings.ADDITIONAL_META_DATA_SETTING), "uid")
|
||||
.build();
|
||||
RealmConfig config = getRealmConfig(REALM_IDENTIFIER, settings);
|
||||
|
||||
SecurityIndexManager mockSecurityIndex = mock(SecurityIndexManager.class);
|
||||
when(mockSecurityIndex.isAvailable()).thenReturn(true);
|
||||
when(mockSecurityIndex.isIndexUpToDate()).thenReturn(true);
|
||||
when(mockSecurityIndex.isMappingUpToDate()).thenReturn(true);
|
||||
|
||||
Client mockClient = mock(Client.class);
|
||||
when(mockClient.threadPool()).thenReturn(threadPool);
|
||||
|
||||
final ScriptService scriptService = new ScriptService(defaultGlobalSettings,
|
||||
Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS);
|
||||
NativeRoleMappingStore roleMapper = new NativeRoleMappingStore(defaultGlobalSettings, mockClient, mockSecurityIndex,
|
||||
scriptService) {
|
||||
@Override
|
||||
protected void loadMappings(ActionListener<List<ExpressionRoleMapping>> listener) {
|
||||
listener.onResponse(
|
||||
Arrays.asList(
|
||||
this.buildMapping("m1", new BytesArray("{" +
|
||||
"\"role_templates\":[{\"template\":{\"source\":\"_user_{{metadata.uid}}\"}}]," +
|
||||
"\"enabled\":true," +
|
||||
"\"rules\":{ \"any\":[" +
|
||||
" { \"field\":{\"realm.name\":\"ldap1\"}}," +
|
||||
" { \"field\":{\"realm.name\":\"ldap2\"}}" +
|
||||
"]}}")),
|
||||
this.buildMapping("m2", new BytesArray("{" +
|
||||
"\"roles\":[\"should_not_happen\"]," +
|
||||
"\"enabled\":true," +
|
||||
"\"rules\":{ \"all\":[" +
|
||||
" { \"field\":{\"realm.name\":\"ldap1\"}}," +
|
||||
" { \"field\":{\"realm.name\":\"ldap2\"}}" +
|
||||
"]}}")),
|
||||
this.buildMapping("m3", new BytesArray("{" +
|
||||
"\"roles\":[\"sales_admin\"]," +
|
||||
"\"enabled\":true," +
|
||||
"\"rules\":" +
|
||||
" { \"field\":{\"dn\":\"*,ou=people,o=sevenSeas\"}}" +
|
||||
"}"))
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
LdapSessionFactory ldapFactory = new LdapSessionFactory(config, sslService, threadPool);
|
||||
LdapRealm ldap = new LdapRealm(config, ldapFactory,
|
||||
roleMapper, threadPool);
|
||||
ldap.initialize(Collections.singleton(ldap), licenseState);
|
||||
|
||||
PlainActionFuture<AuthenticationResult> future = new PlainActionFuture<>();
|
||||
ldap.authenticate(new UsernamePasswordToken("Horatio Hornblower", new SecureString(PASSWORD)), future);
|
||||
final AuthenticationResult result = future.actionGet();
|
||||
assertThat(result.getStatus(), is(AuthenticationResult.Status.SUCCESS));
|
||||
User user = result.getUser();
|
||||
assertThat(user, notNullValue());
|
||||
assertThat(user.roles(), arrayContainingInAnyOrder("_user_hhornblo", "sales_admin"));
|
||||
}
|
||||
|
||||
/**
|
||||
* The contract for {@link Realm} implementations is that they should log-and-return-null (and
|
||||
* not call {@link ActionListener#onFailure(Exception)}) if there is an internal exception that prevented them from performing an
|
||||
|
|
|
@ -5,31 +5,48 @@
|
|||
*/
|
||||
package org.elasticsearch.xpack.security.authc.support.mapper;
|
||||
|
||||
import org.elasticsearch.Version;
|
||||
import org.elasticsearch.common.ParsingException;
|
||||
import org.elasticsearch.common.Strings;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.bytes.BytesReference;
|
||||
import org.elasticsearch.common.io.stream.ByteBufferStreamInput;
|
||||
import org.elasticsearch.common.io.stream.BytesStreamOutput;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
|
||||
import org.elasticsearch.common.io.stream.NamedWriteableRegistry;
|
||||
import org.elasticsearch.common.io.stream.StreamInput;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.common.xcontent.DeprecationHandler;
|
||||
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
|
||||
import org.elasticsearch.common.xcontent.ToXContent;
|
||||
import org.elasticsearch.common.xcontent.XContentFactory;
|
||||
import org.elasticsearch.common.xcontent.XContentParser;
|
||||
import org.elasticsearch.common.xcontent.XContentType;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.test.VersionUtils;
|
||||
import org.elasticsearch.xpack.core.XPackClientPlugin;
|
||||
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.TemplateRoleName;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.AllExpression;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression;
|
||||
import org.elasticsearch.xpack.security.authc.support.UserRoleMapper;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.iterableWithSize;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
public class ExpressionRoleMappingTests extends ESTestCase {
|
||||
|
@ -39,44 +56,44 @@ public class ExpressionRoleMappingTests extends ESTestCase {
|
|||
@Before
|
||||
public void setupMapping() throws Exception {
|
||||
realm = new RealmConfig(new RealmConfig.RealmIdentifier("ldap", "ldap1"),
|
||||
Settings.EMPTY, Mockito.mock(Environment.class), new ThreadContext(Settings.EMPTY));
|
||||
Settings.EMPTY, Mockito.mock(Environment.class), new ThreadContext(Settings.EMPTY));
|
||||
}
|
||||
|
||||
public void testParseValidJson() throws Exception {
|
||||
public void testParseValidJsonWithFixedRoleNames() throws Exception {
|
||||
String json = "{"
|
||||
+ "\"roles\": [ \"kibana_user\", \"sales\" ], "
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"rules\": { "
|
||||
+ " \"all\": [ "
|
||||
+ " { \"field\": { \"dn\" : \"*,ou=sales,dc=example,dc=com\" } }, "
|
||||
+ " { \"except\": { \"field\": { \"metadata.active\" : false } } }"
|
||||
+ " ]}"
|
||||
+ "}";
|
||||
+ "\"roles\": [ \"kibana_user\", \"sales\" ], "
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"rules\": { "
|
||||
+ " \"all\": [ "
|
||||
+ " { \"field\": { \"dn\" : \"*,ou=sales,dc=example,dc=com\" } }, "
|
||||
+ " { \"except\": { \"field\": { \"metadata.active\" : false } } }"
|
||||
+ " ]}"
|
||||
+ "}";
|
||||
final ExpressionRoleMapping mapping = parse(json, "ldap_sales");
|
||||
assertThat(mapping.getRoles(), Matchers.containsInAnyOrder("kibana_user", "sales"));
|
||||
assertThat(mapping.getExpression(), instanceOf(AllExpression.class));
|
||||
|
||||
final UserRoleMapper.UserData user1a = new UserRoleMapper.UserData(
|
||||
"john.smith", "cn=john.smith,ou=sales,dc=example,dc=com",
|
||||
Collections.emptyList(), Collections.singletonMap("active", true), realm
|
||||
"john.smith", "cn=john.smith,ou=sales,dc=example,dc=com",
|
||||
Collections.emptyList(), Collections.singletonMap("active", true), realm
|
||||
);
|
||||
final UserRoleMapper.UserData user1b = new UserRoleMapper.UserData(
|
||||
user1a.getUsername(), user1a.getDn().toUpperCase(Locale.US), user1a.getGroups(), user1a.getMetadata(), user1a.getRealm()
|
||||
user1a.getUsername(), user1a.getDn().toUpperCase(Locale.US), user1a.getGroups(), user1a.getMetadata(), user1a.getRealm()
|
||||
);
|
||||
final UserRoleMapper.UserData user1c = new UserRoleMapper.UserData(
|
||||
user1a.getUsername(), user1a.getDn().replaceAll(",", ", "), user1a.getGroups(), user1a.getMetadata(), user1a.getRealm()
|
||||
user1a.getUsername(), user1a.getDn().replaceAll(",", ", "), user1a.getGroups(), user1a.getMetadata(), user1a.getRealm()
|
||||
);
|
||||
final UserRoleMapper.UserData user1d = new UserRoleMapper.UserData(
|
||||
user1a.getUsername(), user1a.getDn().replaceAll("dc=", "DC="), user1a.getGroups(), user1a.getMetadata(), user1a.getRealm()
|
||||
user1a.getUsername(), user1a.getDn().replaceAll("dc=", "DC="), user1a.getGroups(), user1a.getMetadata(), user1a.getRealm()
|
||||
);
|
||||
final UserRoleMapper.UserData user2 = new UserRoleMapper.UserData(
|
||||
"jamie.perez", "cn=jamie.perez,ou=sales,dc=example,dc=com",
|
||||
Collections.emptyList(), Collections.singletonMap("active", false), realm
|
||||
"jamie.perez", "cn=jamie.perez,ou=sales,dc=example,dc=com",
|
||||
Collections.emptyList(), Collections.singletonMap("active", false), realm
|
||||
);
|
||||
|
||||
final UserRoleMapper.UserData user3 = new UserRoleMapper.UserData(
|
||||
"simone.ng", "cn=simone.ng,ou=finance,dc=example,dc=com",
|
||||
Collections.emptyList(), Collections.singletonMap("active", true), realm
|
||||
"simone.ng", "cn=simone.ng,ou=finance,dc=example,dc=com",
|
||||
Collections.emptyList(), Collections.singletonMap("active", true), realm
|
||||
);
|
||||
|
||||
assertThat(mapping.getExpression().match(user1a.asModel()), equalTo(true));
|
||||
|
@ -87,58 +104,218 @@ public class ExpressionRoleMappingTests extends ESTestCase {
|
|||
assertThat(mapping.getExpression().match(user3.asModel()), equalTo(false));
|
||||
}
|
||||
|
||||
public void testParseValidJsonWithTemplatedRoleNames() throws Exception {
|
||||
String json = "{"
|
||||
+ "\"role_templates\": [ "
|
||||
+ " { \"template\" : { \"source\":\"kibana_user\"} },"
|
||||
+ " { \"template\" : { \"source\":\"sales\"} },"
|
||||
+ " { \"template\" : { \"source\":\"_user_{{username}}\" }, \"format\":\"string\" }"
|
||||
+ " ], "
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"rules\": { "
|
||||
+ " \"all\": [ "
|
||||
+ " { \"field\": { \"dn\" : \"*,ou=sales,dc=example,dc=com\" } }, "
|
||||
+ " { \"except\": { \"field\": { \"metadata.active\" : false } } }"
|
||||
+ " ]}"
|
||||
+ "}";
|
||||
final ExpressionRoleMapping mapping = parse(json, "ldap_sales");
|
||||
assertThat(mapping.getRoleTemplates(), iterableWithSize(3));
|
||||
assertThat(mapping.getRoleTemplates().get(0).getTemplate().utf8ToString(), equalTo("{\"source\":\"kibana_user\"}"));
|
||||
assertThat(mapping.getRoleTemplates().get(0).getFormat(), equalTo(TemplateRoleName.Format.STRING));
|
||||
assertThat(mapping.getRoleTemplates().get(1).getTemplate().utf8ToString(), equalTo("{\"source\":\"sales\"}"));
|
||||
assertThat(mapping.getRoleTemplates().get(1).getFormat(), equalTo(TemplateRoleName.Format.STRING));
|
||||
assertThat(mapping.getRoleTemplates().get(2).getTemplate().utf8ToString(), equalTo("{\"source\":\"_user_{{username}}\"}"));
|
||||
assertThat(mapping.getRoleTemplates().get(2).getFormat(), equalTo(TemplateRoleName.Format.STRING));
|
||||
}
|
||||
|
||||
public void testParsingFailsIfRulesAreMissing() throws Exception {
|
||||
String json = "{"
|
||||
+ "\"roles\": [ \"kibana_user\", \"sales\" ], "
|
||||
+ "\"enabled\": true "
|
||||
+ "}";
|
||||
+ "\"roles\": [ \"kibana_user\", \"sales\" ], "
|
||||
+ "\"enabled\": true "
|
||||
+ "}";
|
||||
ParsingException ex = expectThrows(ParsingException.class, () -> parse(json, "bad_json"));
|
||||
assertThat(ex.getMessage(), containsString("rules"));
|
||||
}
|
||||
|
||||
public void testParsingFailsIfRolesMissing() throws Exception {
|
||||
String json = "{"
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"rules\": "
|
||||
+ " { \"field\": { \"dn\" : \"*,ou=sales,dc=example,dc=com\" } } "
|
||||
+ "}";
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"rules\": "
|
||||
+ " { \"field\": { \"dn\" : \"*,ou=sales,dc=example,dc=com\" } } "
|
||||
+ "}";
|
||||
ParsingException ex = expectThrows(ParsingException.class, () -> parse(json, "bad_json"));
|
||||
assertThat(ex.getMessage(), containsString("role"));
|
||||
}
|
||||
|
||||
public void testParsingFailsIfThereAreUnrecognisedFields() throws Exception {
|
||||
String json = "{"
|
||||
+ "\"disabled\": false, "
|
||||
+ "\"roles\": [ \"kibana_user\", \"sales\" ], "
|
||||
+ "\"rules\": "
|
||||
+ " { \"field\": { \"dn\" : \"*,ou=sales,dc=example,dc=com\" } } "
|
||||
+ "}";
|
||||
+ "\"disabled\": false, "
|
||||
+ "\"roles\": [ \"kibana_user\", \"sales\" ], "
|
||||
+ "\"rules\": "
|
||||
+ " { \"field\": { \"dn\" : \"*,ou=sales,dc=example,dc=com\" } } "
|
||||
+ "}";
|
||||
ParsingException ex = expectThrows(ParsingException.class, () -> parse(json, "bad_json"));
|
||||
assertThat(ex.getMessage(), containsString("disabled"));
|
||||
}
|
||||
|
||||
public void testParsingIgnoresTypeFields() throws Exception {
|
||||
String json = "{"
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"roles\": [ \"kibana_user\", \"sales\" ], "
|
||||
+ "\"rules\": "
|
||||
+ " { \"field\": { \"dn\" : \"*,ou=sales,dc=example,dc=com\" } }, "
|
||||
+ "\"doc_type\": \"role-mapping\", "
|
||||
+ "\"type\": \"doc\""
|
||||
+ "}";
|
||||
final ExpressionRoleMapping mapping = parse(json, "from_index");
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"roles\": [ \"kibana_user\", \"sales\" ], "
|
||||
+ "\"rules\": "
|
||||
+ " { \"field\": { \"dn\" : \"*,ou=sales,dc=example,dc=com\" } }, "
|
||||
+ "\"doc_type\": \"role-mapping\", "
|
||||
+ "\"type\": \"doc\""
|
||||
+ "}";
|
||||
final ExpressionRoleMapping mapping = parse(json, "from_index", true);
|
||||
assertThat(mapping.isEnabled(), equalTo(true));
|
||||
assertThat(mapping.getRoles(), containsInAnyOrder("kibana_user", "sales"));
|
||||
assertThat(mapping.getRoles(), Matchers.containsInAnyOrder("kibana_user", "sales"));
|
||||
}
|
||||
|
||||
public void testParsingOfBothRoleNamesAndTemplates() throws Exception {
|
||||
String json = "{"
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"roles\": [ \"kibana_user\", \"sales\" ], "
|
||||
+ "\"role_templates\": ["
|
||||
+ " { \"template\" : \"{ \\\"source\\\":\\\"_user_{{username}}\\\" }\", \"format\":\"string\" }"
|
||||
+ "],"
|
||||
+ "\"rules\": "
|
||||
+ " { \"field\": { \"dn\" : \"*,ou=sales,dc=example,dc=com\" } }"
|
||||
+ "}";
|
||||
|
||||
// This is rejected when validating a request, but is valid when parsing the mapping
|
||||
final ExpressionRoleMapping mapping = parse(json, "from_api", false);
|
||||
assertThat(mapping.getRoles(), iterableWithSize(2));
|
||||
assertThat(mapping.getRoleTemplates(), iterableWithSize(1));
|
||||
}
|
||||
|
||||
public void testToXContentWithRoleNames() throws Exception {
|
||||
String source = "{"
|
||||
+ "\"roles\": [ "
|
||||
+ " \"kibana_user\","
|
||||
+ " \"sales\""
|
||||
+ " ], "
|
||||
+ "\"enabled\": true, "
|
||||
+ "\"rules\": { \"field\": { \"realm.name\" : \"saml1\" } }"
|
||||
+ "}";
|
||||
final ExpressionRoleMapping mapping = parse(source, getTestName());
|
||||
assertThat(mapping.getRoles(), iterableWithSize(2));
|
||||
|
||||
final String xcontent = Strings.toString(mapping);
|
||||
assertThat(xcontent, equalTo(
|
||||
"{"
|
||||
+ "\"enabled\":true,"
|
||||
+ "\"roles\":["
|
||||
+ "\"kibana_user\","
|
||||
+ "\"sales\""
|
||||
+ "],"
|
||||
+ "\"rules\":{\"field\":{\"realm.name\":\"saml1\"}},"
|
||||
+ "\"metadata\":{}"
|
||||
+ "}"
|
||||
));
|
||||
}
|
||||
|
||||
public void testToXContentWithTemplates() throws Exception {
|
||||
String source = "{"
|
||||
+ "\"metadata\" : { \"answer\":42 },"
|
||||
+ "\"role_templates\": [ "
|
||||
+ " { \"template\" : { \"source\":\"_user_{{username}}\" }, \"format\":\"string\" },"
|
||||
+ " { \"template\" : { \"source\":\"{{#tojson}}groups{{/tojson}}\" }, \"format\":\"json\" }"
|
||||
+ " ], "
|
||||
+ "\"enabled\": false, "
|
||||
+ "\"rules\": { \"field\": { \"realm.name\" : \"saml1\" } }"
|
||||
+ "}";
|
||||
final ExpressionRoleMapping mapping = parse(source, getTestName());
|
||||
assertThat(mapping.getRoleTemplates(), iterableWithSize(2));
|
||||
|
||||
final String xcontent = Strings.toString(mapping.toXContent(XContentFactory.jsonBuilder(), ToXContent.EMPTY_PARAMS, true));
|
||||
assertThat(xcontent, equalTo(
|
||||
"{"
|
||||
+ "\"enabled\":false,"
|
||||
+ "\"role_templates\":["
|
||||
+ "{\"template\":\"{\\\"source\\\":\\\"_user_{{username}}\\\"}\",\"format\":\"string\"},"
|
||||
+ "{\"template\":\"{\\\"source\\\":\\\"{{#tojson}}groups{{/tojson}}\\\"}\",\"format\":\"json\"}"
|
||||
+ "],"
|
||||
+ "\"rules\":{\"field\":{\"realm.name\":\"saml1\"}},"
|
||||
+ "\"metadata\":{\"answer\":42},"
|
||||
+ "\"doc_type\":\"role-mapping\""
|
||||
+ "}"
|
||||
));
|
||||
|
||||
final ExpressionRoleMapping parsed = parse(xcontent, getTestName(), true);
|
||||
assertThat(parsed.getRoles(), iterableWithSize(0));
|
||||
assertThat(parsed.getRoleTemplates(), iterableWithSize(2));
|
||||
assertThat(parsed.getMetadata(), Matchers.hasKey("answer"));
|
||||
}
|
||||
|
||||
public void testSerialization() throws Exception {
|
||||
final ExpressionRoleMapping original = randomRoleMapping(true);
|
||||
|
||||
final Version version = VersionUtils.randomVersionBetween(random(), Version.V_7_1_0, null);
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
output.setVersion(version);
|
||||
original.writeTo(output);
|
||||
|
||||
final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin(Settings.EMPTY).getNamedWriteables());
|
||||
StreamInput streamInput = new NamedWriteableAwareStreamInput(ByteBufferStreamInput.wrap(BytesReference.toBytes(output.bytes())),
|
||||
registry);
|
||||
streamInput.setVersion(version);
|
||||
final ExpressionRoleMapping serialized = new ExpressionRoleMapping(streamInput);
|
||||
assertEquals(original, serialized);
|
||||
}
|
||||
|
||||
public void testSerializationPreV71() throws Exception {
|
||||
final ExpressionRoleMapping original = randomRoleMapping(false);
|
||||
|
||||
final Version version = VersionUtils.randomVersionBetween(random(), Version.V_6_0_0, Version.V_7_0_0);
|
||||
BytesStreamOutput output = new BytesStreamOutput();
|
||||
output.setVersion(version);
|
||||
original.writeTo(output);
|
||||
|
||||
final NamedWriteableRegistry registry = new NamedWriteableRegistry(new XPackClientPlugin(Settings.EMPTY).getNamedWriteables());
|
||||
StreamInput streamInput = new NamedWriteableAwareStreamInput(ByteBufferStreamInput.wrap(BytesReference.toBytes(output.bytes())),
|
||||
registry);
|
||||
streamInput.setVersion(version);
|
||||
final ExpressionRoleMapping serialized = new ExpressionRoleMapping(streamInput);
|
||||
assertEquals(original, serialized);
|
||||
}
|
||||
|
||||
private ExpressionRoleMapping parse(String json, String name) throws IOException {
|
||||
return parse(json, name, false);
|
||||
}
|
||||
|
||||
private ExpressionRoleMapping parse(String json, String name, boolean fromIndex) throws IOException {
|
||||
final NamedXContentRegistry registry = NamedXContentRegistry.EMPTY;
|
||||
final XContentParser parser = XContentType.JSON.xContent()
|
||||
.createParser(registry, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
|
||||
.createParser(registry, DeprecationHandler.THROW_UNSUPPORTED_OPERATION, json);
|
||||
final ExpressionRoleMapping mapping = ExpressionRoleMapping.parse(name, parser);
|
||||
assertThat(mapping, notNullValue());
|
||||
assertThat(mapping.getName(), equalTo(name));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
private ExpressionRoleMapping randomRoleMapping(boolean acceptRoleTemplates) {
|
||||
final boolean useTemplate = acceptRoleTemplates && randomBoolean();
|
||||
final List<String> roles;
|
||||
final List<TemplateRoleName> templates;
|
||||
if (useTemplate) {
|
||||
roles = Collections.emptyList();
|
||||
templates = Arrays.asList(randomArray(1, 5, TemplateRoleName[]::new, () ->
|
||||
new TemplateRoleName(new BytesArray(randomAlphaOfLengthBetween(10, 25)), randomFrom(TemplateRoleName.Format.values()))
|
||||
));
|
||||
} else {
|
||||
roles = Arrays.asList(randomArray(1, 5, String[]::new, () -> randomAlphaOfLengthBetween(4, 12)));
|
||||
templates = Collections.emptyList();
|
||||
}
|
||||
return new ExpressionRoleMapping(
|
||||
randomAlphaOfLengthBetween(3, 8),
|
||||
new FieldExpression(randomAlphaOfLengthBetween(4, 12),
|
||||
Collections.singletonList(new FieldExpression.FieldValue(randomInt(99)))),
|
||||
roles,
|
||||
templates,
|
||||
Collections.singletonMap(randomAlphaOfLengthBetween(3, 12), randomIntBetween(30, 90)),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,10 +10,14 @@ import org.elasticsearch.action.support.PlainActionFuture;
|
|||
import org.elasticsearch.client.Client;
|
||||
import org.elasticsearch.cluster.ClusterName;
|
||||
import org.elasticsearch.cluster.health.ClusterHealthStatus;
|
||||
import org.elasticsearch.common.bytes.BytesArray;
|
||||
import org.elasticsearch.common.settings.Settings;
|
||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||
import org.elasticsearch.env.Environment;
|
||||
import org.elasticsearch.env.TestEnvironment;
|
||||
import org.elasticsearch.script.ScriptModule;
|
||||
import org.elasticsearch.script.ScriptService;
|
||||
import org.elasticsearch.script.mustache.MustacheScriptEngine;
|
||||
import org.elasticsearch.test.ESTestCase;
|
||||
import org.elasticsearch.threadpool.ThreadPool;
|
||||
import org.elasticsearch.xpack.core.security.action.realm.ClearRealmCacheAction;
|
||||
|
@ -23,6 +27,7 @@ import org.elasticsearch.xpack.core.security.authc.AuthenticationResult;
|
|||
import org.elasticsearch.xpack.core.security.authc.RealmConfig;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.ExpressionRoleMapping;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.TemplateRoleName;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression;
|
||||
import org.elasticsearch.xpack.core.security.authc.support.mapper.expressiondsl.FieldExpression.FieldValue;
|
||||
import org.elasticsearch.xpack.core.security.index.RestrictedIndicesNames;
|
||||
|
@ -54,12 +59,12 @@ public class NativeRoleMappingStoreTests extends ESTestCase {
|
|||
// Does match DN
|
||||
final ExpressionRoleMapping mapping1 = new ExpressionRoleMapping("dept_h",
|
||||
new FieldExpression("dn", Collections.singletonList(new FieldValue("*,ou=dept_h,o=forces,dc=gc,dc=ca"))),
|
||||
Arrays.asList("dept_h", "defence"), Collections.emptyMap(), true);
|
||||
Arrays.asList("dept_h", "defence"), Collections.emptyList(), Collections.emptyMap(), true);
|
||||
// Does not match - user is not in this group
|
||||
final ExpressionRoleMapping mapping2 = new ExpressionRoleMapping("admin",
|
||||
new FieldExpression("groups", Collections.singletonList(
|
||||
new FieldValue(randomiseDn("cn=esadmin,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")))),
|
||||
Arrays.asList("admin"), Collections.emptyMap(), true);
|
||||
new FieldExpression("groups", Collections.singletonList(
|
||||
new FieldValue(randomiseDn("cn=esadmin,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")))),
|
||||
Arrays.asList("admin"), Collections.emptyList(), Collections.emptyMap(), true);
|
||||
// Does match - user is one of these groups
|
||||
final ExpressionRoleMapping mapping3 = new ExpressionRoleMapping("flight",
|
||||
new FieldExpression("groups", Arrays.asList(
|
||||
|
@ -67,18 +72,23 @@ public class NativeRoleMappingStoreTests extends ESTestCase {
|
|||
new FieldValue(randomiseDn("cn=betaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")),
|
||||
new FieldValue(randomiseDn("cn=gammaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca"))
|
||||
)),
|
||||
Arrays.asList("flight"), Collections.emptyMap(), true);
|
||||
Collections.emptyList(),
|
||||
Arrays.asList(new TemplateRoleName(new BytesArray("{ \"source\":\"{{metadata.extra_group}}\" }"),
|
||||
TemplateRoleName.Format.STRING)),
|
||||
Collections.emptyMap(), true);
|
||||
// Does not match - mapping is not enabled
|
||||
final ExpressionRoleMapping mapping4 = new ExpressionRoleMapping("mutants",
|
||||
new FieldExpression("groups", Collections.singletonList(
|
||||
new FieldValue(randomiseDn("cn=mutants,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")))),
|
||||
Arrays.asList("mutants"), Collections.emptyMap(), false);
|
||||
Arrays.asList("mutants"), Collections.emptyList(), Collections.emptyMap(), false);
|
||||
|
||||
final Client client = mock(Client.class);
|
||||
SecurityIndexManager securityIndex = mock(SecurityIndexManager.class);
|
||||
ScriptService scriptService = new ScriptService(Settings.EMPTY,
|
||||
Collections.singletonMap(MustacheScriptEngine.NAME, new MustacheScriptEngine()), ScriptModule.CORE_CONTEXTS);
|
||||
when(securityIndex.isAvailable()).thenReturn(true);
|
||||
|
||||
final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, client, securityIndex) {
|
||||
final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, client, securityIndex, scriptService) {
|
||||
@Override
|
||||
protected void loadMappings(ActionListener<List<ExpressionRoleMapping>> listener) {
|
||||
final List<ExpressionRoleMapping> mappings = Arrays.asList(mapping1, mapping2, mapping3, mapping4);
|
||||
|
@ -96,7 +106,7 @@ public class NativeRoleMappingStoreTests extends ESTestCase {
|
|||
Arrays.asList(
|
||||
randomiseDn("cn=alphaflight,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca"),
|
||||
randomiseDn("cn=mutants,ou=groups,ou=dept_h,o=forces,dc=gc,dc=ca")
|
||||
), Collections.emptyMap(), realm);
|
||||
), Collections.singletonMap("extra_group", "flight"), realm);
|
||||
|
||||
logger.info("UserData is [{}]", user);
|
||||
store.resolveRoles(user, future);
|
||||
|
@ -213,7 +223,8 @@ public class NativeRoleMappingStoreTests extends ESTestCase {
|
|||
listener.onResponse(null);
|
||||
}
|
||||
};
|
||||
final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, client, mock(SecurityIndexManager.class));
|
||||
final NativeRoleMappingStore store = new NativeRoleMappingStore(Settings.EMPTY, client, mock(SecurityIndexManager.class),
|
||||
mock(ScriptService.class));
|
||||
store.refreshRealmOnChange(mockRealm);
|
||||
return store;
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
}
|
||||
},
|
||||
"body": {
|
||||
"description" : "The role to add",
|
||||
"description" : "The role mapping to add",
|
||||
"required" : true
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue