feat: implement simple permission matching

This commit is contained in:
TopchetoEU 2023-11-13 19:09:33 +02:00
parent 1eeac3ae97
commit 455f5a613e
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
2 changed files with 178 additions and 0 deletions

View File

@ -0,0 +1,170 @@
package me.topchetoeu.jscript.permissions;
import java.util.ArrayList;
public class Permission {
public final String[][] segments;
private boolean matchSeg(String a, String b) {
if (a.contains("**") || b.contains("**")) throw new IllegalArgumentException("A '**' segment may not contain other characters.");
var segA = a.split("\\*", -1);
var segB = b.split("\\*", -1);
if (segA.length == 1 || segB.length == 1) {
if (segA.length == 1 && segB.length == 1) return a.equals(b);
else if (segA.length == 1) return matchSeg(b, a);
else {
if (!b.startsWith(segA[0]) || !b.endsWith(segA[segA.length - 1])) return false;
int end = b.length() - segA[segA.length - 1].length();
for (int i = 1, findI = 1; i < segA.length - 1; i++) {
findI = b.indexOf(segA[i], findI);
if (findI < 0 || findI + segA[i].length() > end) return false;
}
return true;
}
}
String firstA = segA[0], firstB = segB[0];
String lastA = segA[segA.length - 1], lastB = segB[segB.length - 1];
if (!firstA.startsWith(firstB) && !firstB.startsWith(firstA)) return false;
if (!lastA.endsWith(lastB) && !lastB.endsWith(lastA)) return false;
return true;
}
private boolean matchArrs(String[] a, String[] b, int start, int end) {
if (a.length != end - start) return false;
if (a.length == 0) return true;
if (start >= b.length || end > b.length) return false;
if (start < 0 || end <= 0) return false;
for (var i = start; i < end; i++) {
if (!matchSeg(a[i - start], b[i])) return false;
}
return true;
}
private boolean matchFull(String[] a, String[] b) {
return matchArrs(a, b, 0, b.length);
}
private boolean matchStart(String[] a, String[] b) {
return matchArrs(a, b, 0, a.length);
}
private boolean matchEnd(String[] a, String[] b) {
return matchArrs(a, b, b.length - a.length, b.length);
}
private int find(String[] query, String[] target, int start, int end) {
var findI = 0;
if (start < 0) start = 0;
if (query.length == 0) return start;
if (start != 0 && start >= target.length) return -1;
for (var i = start; i < end; i++) {
if (findI == query.length) return i - findI;
else if (matchSeg(query[findI], target[i])) findI++;
else {
i -= findI;
findI = 0;
}
}
return -1;
}
public boolean match(Permission other) {
var an = this.segments.length;
var bn = other.segments.length;
// We must have at least one segment, even if empty
if (an == 0 || bn == 0) throw new IllegalArgumentException("Can't have a permission with 0 segments.");
if (an == 1 || bn == 1) {
// If both perms are one segment, we just match the segments themselves
if (an == 1 && bn == 1) return matchFull(this.segments[0], other.segments[0]);
else if (an == 1) return other.match(this);
else {
// If just the other perm is one segment, we neet to find all
// the segments of this perm sequentially in the other segment.
var seg = other.segments[0];
// Here we check for the prefix and suffix
if (!matchStart(this.segments[0], seg)) return false;
if (!matchEnd(this.segments[this.segments.length - 1], seg)) return false;
int end = seg.length - this.segments[this.segments.length - 1].length;
// Here we go and look for the segments one by one, until one isn't found
for (int i = 1, findI = 1; i < this.segments.length - 1; i++) {
findI = find(this.segments[i], seg, findI, end);
if (findI < 0) return false;
}
return true;
}
}
// If both perms have more than one segment (a.k.a both have **),
// we can ignore everything in the middle, as it will always match.
// Instead, we check if the prefixes and suffixes match
var firstA = this.segments[0];
var firstB = other.segments[0];
var lastA = this.segments[this.segments.length - 1];
var lastB = other.segments[other.segments.length - 1];
if (!matchStart(firstA, firstB) && !matchStart(firstB, firstA)) return false;
if (!matchEnd(lastA, lastB) && !matchEnd(lastB, lastA)) return false;
return true;
}
@Override
public String toString() {
var sb = new StringBuilder();
var firstSeg = true;
var firstEl = true;
for (var seg : segments) {
if (!firstSeg) {
if (!firstEl) sb.append(".");
sb.append("**");
}
firstSeg = false;
for (var el : seg) {
if (!firstEl) sb.append(".");
sb.append(el);
firstEl = false;
}
}
return sb.toString();
}
public Permission(String raw) {
var segs = raw.split("\\.");
var curr = new ArrayList<String>();
var res = new ArrayList<String[]>();
for (var seg : segs) {
if (seg.equals("**")) {
res.add(curr.toArray(String[]::new));
curr.clear();
}
else curr.add(seg);
}
res.add(curr.toArray(String[]::new));
segments = res.toArray(String[][]::new);
}
public static boolean match(String a, String b) {
return new Permission(a).match(new Permission(b));
}
}

View File

@ -0,0 +1,8 @@
package me.topchetoeu.jscript.permissions;
public interface PermissionsManager {
public static final PermissionsManager ALL_PERMS = perm -> true;
public static final PermissionsManager NO_PERMS = perm -> false;
boolean hasPermissions(String perm);
}