Skip to content

Commit eb4ef15

Browse files
committed
Change config
1 parent 944e80a commit eb4ef15

13 files changed

Lines changed: 626 additions & 73 deletions

File tree

‎runtime/build.gradle‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ repositories {
1414

1515
ext {
1616
asmVersion = '9.2'
17+
recordBuilderVersion = '26'
1718
}
1819

1920
dependencies {
@@ -24,6 +25,8 @@ dependencies {
2425
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitPlatformVersion"
2526
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:$junitPlatformVersion"
2627
testRuntimeOnly "com.google.flogger:flogger-slf4j-backend:$floggerVersion"
28+
compileOnly "io.soabase.record-builder:record-builder-core:$recordBuilderVersion"
29+
annotationProcessor "io.soabase.record-builder:record-builder-processor:$recordBuilderVersion"
2730
}
2831

2932
test {

‎runtime/src/main/java/jsheets/runtime/evaluation/shell/ShellEvaluation.java‎

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package jsheets.runtime.evaluation.shell;
22

3-
43
import java.time.Clock;
54
import java.util.Comparator;
65
import java.util.Locale;
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
package jsheets.sandbox;
2+
3+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
4+
5+
import java.util.*;
6+
import java.util.regex.Pattern;
7+
8+
public final class AccessGraph {
9+
private static final AccessGraph empty =
10+
new AccessGraph(new Node.Path("root", Access.Denied, List.of()));
11+
12+
public static AccessGraph empty() {
13+
return empty;
14+
}
15+
16+
public static AccessGraph of(String... permissions) {
17+
var builder = newBuilder();
18+
for (var permission : permissions) {
19+
if (permission.startsWith("!")) {
20+
var remaining = permission.substring(1).trim();
21+
builder.deny(Key.infer(remaining));
22+
} else {
23+
builder.permit(Key.infer(permission.trim()));
24+
}
25+
}
26+
return builder.create();
27+
}
28+
29+
public enum Access {
30+
Permitted, Denied
31+
}
32+
33+
/* sealed */ interface Node extends Iterable<Node> {
34+
Access access();
35+
String name();
36+
Collection<Node> children();
37+
void insertChild(Node node);
38+
39+
default Iterator<Node> iterator() {
40+
return children().iterator();
41+
}
42+
43+
record Path(String name, Access access, Collection<Node> children) implements Node {
44+
public Collection<Method> listMethodsByName(String name) {
45+
var methods = new ArrayList<Method>();
46+
for (var child : children) {
47+
if (child instanceof Method method && method.name().equals(name)) {
48+
methods.add(method);
49+
}
50+
}
51+
return methods;
52+
}
53+
54+
@Override
55+
public void insertChild(Node node) {
56+
children.add(node);
57+
}
58+
}
59+
60+
record Method(MethodSignature signature, Access access) implements Node {
61+
@Override
62+
public String name() {
63+
return signature.methodName();
64+
}
65+
66+
@Override
67+
public Collection<Node> children() {
68+
return List.of();
69+
}
70+
71+
@Override
72+
public void insertChild(Node node) {
73+
throw new UnsupportedOperationException("can not add child to method");
74+
}
75+
}
76+
}
77+
78+
public record Key(Pattern separator, String value) {
79+
public static Key infer(String value) {
80+
return value.contains("/") ? slashSeparated(value) : dotSeparated(value);
81+
}
82+
83+
private static final Pattern dotOrMethodSeparator =
84+
Pattern.compile("[.#]");
85+
86+
public static Key dotSeparated(String value) {
87+
Objects.requireNonNull(value, "value");
88+
return new Key(dotOrMethodSeparator, value);
89+
}
90+
91+
private static final Pattern slashOrMethodSeparator =
92+
Pattern.compile("[/#]");
93+
94+
public static Key slashSeparated(String value) {
95+
Objects.requireNonNull(value, "value");
96+
return new Key(slashOrMethodSeparator, value);
97+
}
98+
99+
public String[] split() {
100+
return separator.split(value);
101+
}
102+
103+
public String lastPart() {
104+
var parts = split();
105+
if (parts.length == 0) {
106+
return value;
107+
}
108+
return parts[parts.length - 1];
109+
}
110+
}
111+
112+
private final Node root;
113+
114+
private AccessGraph(Node root) {
115+
this.root = root;
116+
}
117+
118+
private Collection<Node> findClosestMatch(Key key) {
119+
return findClosestMatch(root, key.split(), 0);
120+
}
121+
122+
private static Collection<Node> findClosestMatch(Node node, String[] key, int depth) {
123+
if (depth >= key.length) {
124+
return List.of(node);
125+
}
126+
if (depth == key.length - 1) {
127+
return findClosestMatchOrMethods(node, key[key.length - 1]);
128+
}
129+
return findClosestChild(node, key, depth);
130+
}
131+
132+
private static Collection<Node> findClosestChild(Node node, String[] key, int depth) {
133+
for (var child : node.children()) {
134+
if (child.name().equals(key[depth])) {
135+
return findClosestMatch(child, key, depth + 1);
136+
}
137+
}
138+
return List.of(node);
139+
}
140+
141+
private static Collection<Node> findClosestMatchOrMethods(Node node, String lastKey) {
142+
var children = node.children().stream()
143+
.filter(child -> child.name().equals(lastKey))
144+
.toList();
145+
return children.isEmpty() ? List.of(node) : children;
146+
}
147+
148+
/**
149+
* Checks if the use of the class or method under the given key is permitted.
150+
* <p>
151+
* When checking access to methods prefer {@link AccessGraph#isMethodPermitted(MethodSignature)}.
152+
*
153+
* @param key Key of the checked package, class or method.
154+
* @return True if the caller has permission to use classes or methods
155+
* under this key.
156+
*/
157+
public boolean isPermitted(Key key) {
158+
var matches = findClosestMatch(key);
159+
return !matches.isEmpty()
160+
&& matches.iterator().next().access().equals(Access.Permitted);
161+
}
162+
163+
/**
164+
* Checks if a call to the method is permitted.
165+
* <p>
166+
* This call is better for checking access to methods because it supports
167+
* overloaded methods. The {@link AccessGraph#isPermitted} method does
168+
* not handle ambiguous calls well.
169+
* <p>
170+
* Note, that this method does not support covariant return types.
171+
*
172+
* @param signature Signature of the method that is checked.
173+
* @return True if the caller has permissions to call this method.
174+
*/
175+
public boolean isMethodPermitted(MethodSignature signature) {
176+
var key = Key.slashSeparated(signature.formatWithoutTypes());
177+
var matches = findClosestMatch(key);
178+
var access = switch (matches.size()) {
179+
case 0 -> Access.Denied;
180+
case 1 -> matches.iterator().next().access();
181+
default -> findBestMatch(signature, matches).map(Node::access);
182+
};
183+
return access.equals(Access.Permitted);
184+
}
185+
186+
private Optional<Node> findBestMatch(MethodSignature signature, Collection<Node> nodes) {
187+
for (var node : nodes) {
188+
if (node instanceof Node.Method method
189+
&& method.signature.matches(signature)) {
190+
return Optional.of(node);
191+
}
192+
}
193+
return Optional.empty();
194+
}
195+
196+
@Override
197+
public String toString() {
198+
return "AccessGraph(%s)".formatted(root.children().toString());
199+
}
200+
201+
@Override
202+
public boolean equals(Object target) {
203+
if (this == target) {
204+
return true;
205+
}
206+
return target instanceof AccessGraph graph && (
207+
Objects.equals(graph.root, root)
208+
);
209+
}
210+
211+
@Override
212+
public int hashCode() {
213+
return Objects.hash(root);
214+
}
215+
216+
public static Builder newBuilder() {
217+
return new Builder();
218+
}
219+
220+
public static final class Builder {
221+
private final Node root =
222+
new Node.Path("root", Access.Denied, new ArrayList<>());
223+
224+
private Builder() {}
225+
226+
@CanIgnoreReturnValue
227+
public Builder permit(Key key) {
228+
var node = createNode(key, Access.Permitted);
229+
insert(key, node);
230+
return this;
231+
}
232+
233+
@CanIgnoreReturnValue
234+
public Builder deny(Key key) {
235+
var node = createNode(key, Access.Denied);
236+
insert(key, node);
237+
return this;
238+
}
239+
240+
private void insert(Key key, Node node) {
241+
var parent = resolveParent(key, node);
242+
parent.insertChild(node);
243+
}
244+
245+
private Node resolveParent(Key key, Node inserted) {
246+
var path = key.split();
247+
if (path.length == 1) {
248+
return root;
249+
}
250+
var parentPath = Arrays.copyOf(path, path.length - 1);
251+
return resolveParent(root, parentPath, 0, inserted);
252+
}
253+
254+
private Node resolveParent(Node target, String[] key, int depth, Node inserted) {
255+
if (depth >= key.length) {
256+
return target;
257+
}
258+
for (var child : target.children()) {
259+
if (child.name().equals(key[depth])) {
260+
return resolveParent(child, key, depth + 1, inserted);
261+
}
262+
}
263+
return insertRemainingPath(target, key, depth, inserted.access());
264+
}
265+
266+
private Node insertRemainingPath(Node target, String[] key, int depth, Access access) {
267+
for (int index = depth; index < key.length; index++) {
268+
var intermediate = new Node.Path(key[index], access, new ArrayList<>());
269+
target.insertChild(intermediate);
270+
target = intermediate;
271+
}
272+
return target;
273+
}
274+
275+
private Node createNode(Key key, Access access) {
276+
if (isMethodSignature(key.value())) {
277+
var signature = MethodSignature.parse(key.value());
278+
return new Node.Method(signature, access);
279+
}
280+
return new Node.Path(key.lastPart(), access, new ArrayList<>());
281+
}
282+
283+
private static boolean isMethodSignature(String path) {
284+
return path.indexOf(MethodSignature.methodSeparator()) >= 0;
285+
}
286+
287+
// TODO: Create defensive copies after building
288+
public AccessGraph create() {
289+
return new AccessGraph(root);
290+
}
291+
}
292+
}

0 commit comments

Comments
 (0)