Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HLRC] Support for role mapper expression dsl #33745

Merged
merged 12 commits into from
Sep 27, 2018
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.support.expressiondsl;

import org.elasticsearch.common.xcontent.ToXContentObject;

/**
* Implementations of this interface represent an expression used for user role mapping
* that can later be resolved to a boolean value.
*/
public interface RoleMapperExpression extends ToXContentObject {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/*
* 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.support.expressiondsl.expressions;

import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.xcontent.XContentBuilder;

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.Objects;

/**
* Expression of role mapper expressions which can be combined by operations like AND, OR
* <p>
* Expression builder example:
* <pre>
* {@code
* final RoleMapperExpression allExpression = CompositeRoleMapperExpression.builder(CompositeType.ALL)
.addExpression(CompositeRoleMapperExpression.builder(CompositeType.ANY)
.addExpression(FieldRoleMapperExpression.builder(FieldType.USERNAME)
.addValue("user1@example.org")
.build())
.addExpression(FieldRoleMapperExpression.builder(FieldType.USERNAME)
.addValue("user2@example.org")
.build())
.build())
.addExpression(FieldRoleMapperExpression.builder(FieldType.METADATA)
.withKey("metadata.location")
.addValue("AMER")
.build())
.build();
* }
* </pre>
*/
public class CompositeRoleMapperExpression implements RoleMapperExpression {
private final String name;
private final List<RoleMapperExpression> elements;

CompositeRoleMapperExpression(final String name, final RoleMapperExpression... elements) {
assert name != null : "field name cannot be null";
assert elements != null : "at least one field expression is required";
this.name = name;
this.elements = Collections.unmodifiableList(Arrays.asList(elements));
}

public String getName() {
return this.getName();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/return this.getName();/return this.name;/
Noticed this when reviewing #34171 , maybe you can correct this there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I will address in the other review for the create role mapping API. Thank you.

}

public List<RoleMapperExpression> getElements() {
return elements;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}

final CompositeRoleMapperExpression that = (CompositeRoleMapperExpression) o;
if (Objects.equals(this.getName(), that.getName()) == false) {
return false;
}
return Objects.equals(this.getElements(), that.getElements());
}

@Override
public int hashCode() {
return Objects.hash(name, elements);
}

@Override
public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException {
builder.startObject();
if (CompositeType.EXCEPT.getName().equals(name)) {
bizybot marked this conversation as resolved.
Show resolved Hide resolved
builder.field(name);
builder.value(elements.get(0));
} else {
builder.startArray(name);
for (RoleMapperExpression e : elements) {
e.toXContent(builder, params);
}
builder.endArray();
}
return builder.endObject();
}

/**
* Generates composite role mapper expression builder for give
* {@link CompositeType}
*
* @param type {@link CompositeType} composite role expression types like
* all, any etc.
* @return {@link Builder} instance
*/
public static Builder builder(CompositeType type) {
return new Builder(type);
}

public enum CompositeType {
ANY("any"), ALL("all"), EXCEPT("except");

private static Map<String, CompositeType> nameToType = Collections.unmodifiableMap(initialize());
private ParseField field;

CompositeType(String name) {
this.field = new ParseField(name);
}

public String getName() {
return field.getPreferredName();
}

public ParseField getParseField() {
return field;
}

public static CompositeType fromName(String name) {
return nameToType.get(name);
}

private static Map<String, CompositeType> initialize() {
Map<String, CompositeType> map = new HashMap<>();
for (CompositeType field : values()) {
map.put(field.getName(), field);
}
return map;
}
}

public static final class Builder {
private CompositeType field;
private List<RoleMapperExpression> elements = new ArrayList<>();

Builder(CompositeType field) {
this.field = field;
}

public Builder addExpression(final RoleMapperExpression expression) {
assert expression != null : "expression cannot be null";
if (CompositeType.EXCEPT == field) {
assert elements.isEmpty() : "except expression can have only one delegate expression";
}
elements.add(expression);
return this;
}

public CompositeRoleMapperExpression build() {
return new CompositeRoleMapperExpression(field.getName(), elements.toArray(new RoleMapperExpression[0]));
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/*
* 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.support.expressiondsl.fields;

import org.elasticsearch.client.security.support.expressiondsl.RoleMapperExpression;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.XContentBuilder;

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.Objects;

/**
* An expression that evaluates to <code>true</code> if a field (map element) matches
* the provided values. A <em>field</em> expression may have more than one provided value, in which
* case the expression is true if <em>any</em> of the values are matched.
* <p>
* Expression builder example:
* <pre>
* {@code
* final RoleMapperExpression usernameExpression = FieldRoleMapperExpression.builder(FieldType.USERNAME)
.addValue("user1@example.org")
.build();
* }
* </pre>
*/
public class FieldRoleMapperExpression implements RoleMapperExpression {
private final String NAME = "field";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/NAME/name

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed this, Thanks.


private final String field;
private final List<Object> values;

public FieldRoleMapperExpression(final String field, final Object... values) {
if (field == null || field.isEmpty()) {
throw new IllegalArgumentException("null or empty field name (" + field + ")");
}
if (values == null || values.length == 0) {
throw new IllegalArgumentException("null or empty values (" + values + ")");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In case of an empty array, this will invoke toString() on the array which will offer no useful information. Maybe change this to

throw new IllegalArgumentException("null or empty values for field (" + field + ")");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, will address this in another review.

}
this.field = field;
this.values = Collections.unmodifiableList(Arrays.asList(values));
}

public String getField() {
return field;
}

public List<Object> getValues() {
return values;
}

@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;

final FieldRoleMapperExpression that = (FieldRoleMapperExpression) o;

return Objects.equals(this.getField(), that.getField()) && Objects.equals(this.getValues(), that.getValues());
}

@Override
public int hashCode() {
int result = field.hashCode();
result = 31 * result + values.hashCode();
return result;
}

@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.startObject(NAME);
if (this.values.size() == 1) {
bizybot marked this conversation as resolved.
Show resolved Hide resolved
builder.field(this.field);
builder.value(values.get(0));
} else {
builder.startArray(this.field);
for (Object value : values) {
builder.value(value);
}
builder.endArray();
}
builder.endObject();
return builder.endObject();
}

/**
* Generates field role mapper expression builder for give {@link FieldType}
*
* @param type {@link FieldType} composite role expression types like
* username, groups etc.
* @return {@link Builder} instance
*/
public static Builder builder(FieldType type) {
return new Builder(type);
}

public enum FieldType {
bizybot marked this conversation as resolved.
Show resolved Hide resolved
FIELD("field"), USERNAME("username"), GROUPS("groups"), DN("dn"), METADATA("metadata.");

private static Map<String, FieldType> nameToType = Collections.unmodifiableMap(initialize());
private ParseField field;

FieldType(String name) {
this.field = new ParseField(name);
}

public String getName() {
return field.getPreferredName();
}

public ParseField getParseField() {
return field;
}

public static FieldType fromName(String name) {
return nameToType.get(name);
}

private static Map<String, FieldType> initialize() {
Map<String, FieldType> map = new HashMap<>();
for (FieldType field : values()) {
map.put(field.getName(), field);
}
return map;
}
}

public static final class Builder {
private FieldType field;
private String key;
private List<Object> elements = new ArrayList<>();

Builder(FieldType field) {
this.field = field;
}

public Builder withKey(String key) {
assert Strings.hasLength(key) : "metadata key cannot be null or empty";
assert FieldType.METADATA == field : "metadata key can only be provided when building MetadataFieldExpression";
this.key = key;
return this;
}

public Builder addValue(Object value) {
elements.add(value);
return this;
}

public FieldRoleMapperExpression build() {
if (FieldType.METADATA == field) {
String fieldName = key.startsWith(FieldType.METADATA.getName()) ? key : FieldType.METADATA.getName() + key;
return new FieldRoleMapperExpression(fieldName, elements.toArray());
} else {
return new FieldRoleMapperExpression(field.getName(), elements.toArray());
}
}
}
}
Loading