Skip to content

Commit 6050deb

Browse files
HLRest: model role and privileges (#35128)
The following "user privileges" objects have been created on the client side: * ApplicationResourcePrivileges * IndicesPrivileges * GlobalOperationPrivilege * GlobalPrivileges as well as the aggregating `Role` entity.
1 parent eb8f346 commit 6050deb

File tree

8 files changed

+1238
-0
lines changed

8 files changed

+1238
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.security.user.privileges;
21+
22+
import org.elasticsearch.common.ParseField;
23+
import org.elasticsearch.common.Strings;
24+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
25+
import org.elasticsearch.common.xcontent.ToXContentObject;
26+
import org.elasticsearch.common.xcontent.XContentBuilder;
27+
import org.elasticsearch.common.xcontent.XContentHelper;
28+
import org.elasticsearch.common.xcontent.XContentParser;
29+
import org.elasticsearch.common.xcontent.XContentType;
30+
31+
import java.io.IOException;
32+
import java.util.Collection;
33+
import java.util.Collections;
34+
import java.util.HashSet;
35+
import java.util.Objects;
36+
import java.util.Set;
37+
38+
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg;
39+
40+
/**
41+
* Represents privileges over resources that are scoped under an application.
42+
* The application, resources and privileges are completely managed by the
43+
* client and can be arbitrary string identifiers. Elasticsearch is not
44+
* concerned by any resources under an application scope.
45+
*/
46+
public final class ApplicationResourcePrivileges implements ToXContentObject {
47+
48+
private static final ParseField APPLICATION = new ParseField("application");
49+
private static final ParseField PRIVILEGES = new ParseField("privileges");
50+
private static final ParseField RESOURCES = new ParseField("resources");
51+
52+
@SuppressWarnings("unchecked")
53+
static final ConstructingObjectParser<ApplicationResourcePrivileges, Void> PARSER = new ConstructingObjectParser<>(
54+
"application_privileges", false, constructorObjects -> {
55+
// Don't ignore unknown fields. It is dangerous if the object we parse is also
56+
// part of a request that we build later on, and the fields that we now ignore will
57+
// end up being implicitly set to null in that request.
58+
int i = 0;
59+
final String application = (String) constructorObjects[i++];
60+
final Collection<String> privileges = (Collection<String>) constructorObjects[i++];
61+
final Collection<String> resources = (Collection<String>) constructorObjects[i];
62+
return new ApplicationResourcePrivileges(application, privileges, resources);
63+
});
64+
65+
static {
66+
PARSER.declareString(constructorArg(), APPLICATION);
67+
PARSER.declareStringArray(constructorArg(), PRIVILEGES);
68+
PARSER.declareStringArray(constructorArg(), RESOURCES);
69+
}
70+
71+
private final String application;
72+
private final Set<String> privileges;
73+
private final Set<String> resources;
74+
75+
/**
76+
* Constructs privileges for resources under an application scope.
77+
*
78+
* @param application
79+
* The application name. This identifier is completely under the
80+
* clients control.
81+
* @param privileges
82+
* The privileges names. Cannot be null or empty. Privilege
83+
* identifiers are completely under the clients control.
84+
* @param resources
85+
* The resources names. Cannot be null or empty. Resource identifiers
86+
* are completely under the clients control.
87+
*/
88+
public ApplicationResourcePrivileges(String application, Collection<String> privileges, Collection<String> resources) {
89+
if (Strings.isNullOrEmpty(application)) {
90+
throw new IllegalArgumentException("application privileges must have an application name");
91+
}
92+
if (null == privileges || privileges.isEmpty()) {
93+
throw new IllegalArgumentException("application privileges must define at least one privilege");
94+
}
95+
if (null == resources || resources.isEmpty()) {
96+
throw new IllegalArgumentException("application privileges must refer to at least one resource");
97+
}
98+
this.application = application;
99+
this.privileges = Collections.unmodifiableSet(new HashSet<>(privileges));
100+
this.resources = Collections.unmodifiableSet(new HashSet<>(resources));
101+
}
102+
103+
public String getApplication() {
104+
return application;
105+
}
106+
107+
public Set<String> getResources() {
108+
return this.resources;
109+
}
110+
111+
public Set<String> getPrivileges() {
112+
return this.privileges;
113+
}
114+
115+
@Override
116+
public boolean equals(Object o) {
117+
if (this == o) {
118+
return true;
119+
}
120+
if (o == null || this.getClass() != o.getClass()) {
121+
return false;
122+
}
123+
ApplicationResourcePrivileges that = (ApplicationResourcePrivileges) o;
124+
return application.equals(that.application)
125+
&& privileges.equals(that.privileges)
126+
&& resources.equals(that.resources);
127+
}
128+
129+
@Override
130+
public int hashCode() {
131+
return Objects.hash(application, privileges, resources);
132+
}
133+
134+
@Override
135+
public String toString() {
136+
try {
137+
return XContentHelper.toXContent(this, XContentType.JSON, true).utf8ToString();
138+
} catch (IOException e) {
139+
throw new RuntimeException("Unexpected", e);
140+
}
141+
}
142+
143+
@Override
144+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
145+
builder.startObject();
146+
builder.field(APPLICATION.getPreferredName(), application);
147+
builder.field(PRIVILEGES.getPreferredName(), privileges);
148+
builder.field(RESOURCES.getPreferredName(), resources);
149+
return builder.endObject();
150+
}
151+
152+
public static ApplicationResourcePrivileges fromXContent(XContentParser parser) {
153+
return PARSER.apply(parser, null);
154+
}
155+
156+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.security.user.privileges;
21+
22+
import org.elasticsearch.common.xcontent.XContentParser;
23+
24+
import java.io.IOException;
25+
import java.util.Collections;
26+
import java.util.Map;
27+
import java.util.Objects;
28+
29+
/**
30+
* Represents generic global cluster privileges that can be scoped by categories
31+
* and then further by operations. The privilege's syntactic and semantic
32+
* meaning is specific to each category and operation; there is no general
33+
* definition template. It is not permitted to define different privileges under
34+
* the same category and operation.
35+
*/
36+
public class GlobalOperationPrivilege {
37+
38+
private final String category;
39+
private final String operation;
40+
private final Map<String, Object> privilege;
41+
42+
/**
43+
* Constructs privileges under a specific {@code category} and for some
44+
* {@code operation}. The privilege definition is flexible, it is a {@code Map},
45+
* and the semantics is bound to the {@code category} and {@code operation}.
46+
*
47+
* @param category
48+
* The category of the privilege.
49+
* @param operation
50+
* The operation of the privilege.
51+
* @param privilege
52+
* The privilege definition.
53+
*/
54+
public GlobalOperationPrivilege(String category, String operation, Map<String, Object> privilege) {
55+
this.category = Objects.requireNonNull(category);
56+
this.operation = Objects.requireNonNull(operation);
57+
if (privilege == null || privilege.isEmpty()) {
58+
throw new IllegalArgumentException("Privileges cannot be empty or null");
59+
}
60+
this.privilege = Collections.unmodifiableMap(privilege);
61+
}
62+
63+
public String getCategory() {
64+
return category;
65+
}
66+
67+
public String getOperation() {
68+
return operation;
69+
}
70+
71+
public Map<String, Object> getRaw() {
72+
return privilege;
73+
}
74+
75+
public static GlobalOperationPrivilege fromXContent(String category, String operation, XContentParser parser) throws IOException {
76+
// parser is still placed on the field name, advance to next token (field value)
77+
assert parser.currentToken().equals(XContentParser.Token.FIELD_NAME);
78+
parser.nextToken();
79+
return new GlobalOperationPrivilege(category, operation, parser.map());
80+
}
81+
82+
@Override
83+
public boolean equals(Object o) {
84+
if (this == o) {
85+
return true;
86+
}
87+
if (o == null || (false == this instanceof GlobalOperationPrivilege)) {
88+
return false;
89+
}
90+
final GlobalOperationPrivilege that = (GlobalOperationPrivilege) o;
91+
return category.equals(that.category) && operation.equals(that.operation) && privilege.equals(that.privilege);
92+
}
93+
94+
@Override
95+
public int hashCode() {
96+
return Objects.hash(category, operation, privilege);
97+
}
98+
99+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.client.security.user.privileges;
21+
22+
import org.elasticsearch.common.ParseField;
23+
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
24+
import org.elasticsearch.common.xcontent.ToXContentObject;
25+
import org.elasticsearch.common.xcontent.XContentBuilder;
26+
import org.elasticsearch.common.xcontent.XContentParser;
27+
28+
import java.io.IOException;
29+
import java.util.Arrays;
30+
import java.util.Collection;
31+
import java.util.Collections;
32+
import java.util.HashSet;
33+
import java.util.List;
34+
import java.util.Map;
35+
import java.util.Objects;
36+
import java.util.Set;
37+
import java.util.stream.Collectors;
38+
39+
import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg;
40+
41+
/**
42+
* Represents global privileges. "Global Privilege" is a mantra for granular
43+
* generic cluster privileges. These privileges are organized into categories.
44+
* Elasticsearch defines the set of categories. Under each category there are
45+
* operations that are under the clients jurisdiction. The privilege is hence
46+
* defined under an operation under a category.
47+
*/
48+
public final class GlobalPrivileges implements ToXContentObject {
49+
50+
// When categories change, adapting this field should suffice. Categories are NOT
51+
// opaque "named_objects", we wish to maintain control over these namespaces
52+
static final List<String> CATEGORIES = Collections.unmodifiableList(Arrays.asList("application"));
53+
54+
@SuppressWarnings("unchecked")
55+
static final ConstructingObjectParser<GlobalPrivileges, Void> PARSER = new ConstructingObjectParser<>("global_category_privileges",
56+
false, constructorObjects -> {
57+
// ignore_unknown_fields is irrelevant here anyway, but let's keep it to false
58+
// because this conveys strictness (woop woop)
59+
return new GlobalPrivileges((Collection<GlobalOperationPrivilege>) constructorObjects[0]);
60+
});
61+
62+
static {
63+
for (final String category : CATEGORIES) {
64+
PARSER.declareNamedObjects(optionalConstructorArg(),
65+
(parser, context, operation) -> GlobalOperationPrivilege.fromXContent(category, operation, parser),
66+
new ParseField(category));
67+
}
68+
}
69+
70+
private final Set<? extends GlobalOperationPrivilege> privileges;
71+
// same data as in privileges but broken down by categories; internally, it is
72+
// easier to work with this structure
73+
private final Map<String, List<GlobalOperationPrivilege>> privilegesByCategoryMap;
74+
75+
/**
76+
* Constructs global privileges by bundling the set of privileges.
77+
*
78+
* @param privileges
79+
* The privileges under a category and for an operation in that category.
80+
*/
81+
public GlobalPrivileges(Collection<? extends GlobalOperationPrivilege> privileges) {
82+
if (privileges == null || privileges.isEmpty()) {
83+
throw new IllegalArgumentException("Privileges cannot be empty or null");
84+
}
85+
// duplicates are just ignored
86+
this.privileges = Collections.unmodifiableSet(new HashSet<>(Objects.requireNonNull(privileges)));
87+
this.privilegesByCategoryMap = Collections
88+
.unmodifiableMap(this.privileges.stream().collect(Collectors.groupingBy(GlobalOperationPrivilege::getCategory)));
89+
for (final Map.Entry<String, List<GlobalOperationPrivilege>> privilegesByCategory : privilegesByCategoryMap.entrySet()) {
90+
// all operations for a specific category
91+
final Set<String> allOperations = privilegesByCategory.getValue().stream().map(p -> p.getOperation())
92+
.collect(Collectors.toSet());
93+
if (allOperations.size() != privilegesByCategory.getValue().size()) {
94+
throw new IllegalArgumentException("Different privileges for the same category and operation are not permitted");
95+
}
96+
}
97+
}
98+
99+
@Override
100+
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
101+
builder.startObject();
102+
for (final Map.Entry<String, List<GlobalOperationPrivilege>> privilegesByCategory : this.privilegesByCategoryMap.entrySet()) {
103+
builder.startObject(privilegesByCategory.getKey());
104+
for (final GlobalOperationPrivilege privilege : privilegesByCategory.getValue()) {
105+
builder.field(privilege.getOperation(), privilege.getRaw());
106+
}
107+
builder.endObject();
108+
}
109+
return builder.endObject();
110+
}
111+
112+
public static GlobalPrivileges fromXContent(XContentParser parser) {
113+
return PARSER.apply(parser, null);
114+
}
115+
116+
public Set<? extends GlobalOperationPrivilege> getPrivileges() {
117+
return privileges;
118+
}
119+
120+
@Override
121+
public boolean equals(Object o) {
122+
if (this == o) {
123+
return true;
124+
}
125+
if (o == null || this.getClass() != o.getClass()) {
126+
return false;
127+
}
128+
final GlobalPrivileges that = (GlobalPrivileges) o;
129+
return privileges.equals(that.privileges);
130+
}
131+
132+
@Override
133+
public int hashCode() {
134+
return Objects.hash(privileges);
135+
}
136+
137+
}

0 commit comments

Comments
 (0)