Skip to content

Commit

Permalink
feat: Major improvements in the rank permission system
Browse files Browse the repository at this point in the history
  • Loading branch information
phinner committed Apr 23, 2024
1 parent 0e9eca4 commit aa95634
Show file tree
Hide file tree
Showing 26 changed files with 673 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,9 @@
import com.xpdustry.distributor.api.DistributorProvider;
import com.xpdustry.distributor.api.collection.MindustryCollections;
import com.xpdustry.distributor.api.command.cloud.MindustryCaptionKeys;
import com.xpdustry.distributor.api.command.cloud.MindustryCommandContextKeys;
import com.xpdustry.distributor.api.player.PlayerLookup;
import java.util.ArrayList;
import java.util.Locale;
import java.util.concurrent.CompletableFuture;
import mindustry.gen.Groups;
import mindustry.gen.Player;
Expand Down Expand Up @@ -53,10 +54,14 @@ public static <C> CommandComponent.Builder<C, Player> playerComponent() {
@Override
public ArgumentParseResult<Player> parse(final CommandContext<C> ctx, final CommandInput input) {
final var queryBuilder = PlayerLookup.Query.builder().setInput(input.readString());
if (ctx.getOrDefault(MindustryCommandContextKeys.ADMIN, false)) {
queryBuilder.addField(PlayerLookup.Field.UUID);
final var fields = new ArrayList<PlayerLookup.Field>();
for (final var field : PlayerLookup.Field.values()) {
if (ctx.hasPermission("distributor.player.lookup." + field.name().toLowerCase(Locale.ROOT))) {
fields.add(field);
}
}
final var query = queryBuilder.build();
;
final var query = queryBuilder.setFields(fields).build();
final var players = DistributorProvider.get().getPlayerLookup().findOnlinePlayers(query);
if (players.isEmpty()) {
return ArgumentParseResult.failure(new PlayerParseException.PlayerNotFound(query.getInput(), ctx));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ public TriState getPermission(final String permission) {
}

@Override
public void setPermission(final String permission, final TriState state) {
throw new UnsupportedOperationException();
public Map<String, Boolean> getPermissions() {
return Collections.emptyMap();
}

@Override
public Map<String, Boolean> getPermissions() {
return Collections.emptyMap();
public String toString() {
return this.getClass().getSimpleName();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ public TriState getPermission(final String permission) {
return inner.getPermission(permission);
}

@Override
public void setPermission(final String permission, final TriState state) {
throw new UnsupportedOperationException();
}

@Override
public Map<String, Boolean> getPermissions() {
return inner.getPermissions();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Distributor, a feature-rich framework for Mindustry plugins.
*
* Copyright (C) 2024 Xpdustry
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.xpdustry.distributor.api.permission;

public interface MutablePermissionTree extends PermissionTree {

static MutablePermissionTree create() {
return new MutablePermissionTreeImpl();
}

static MutablePermissionTree from(final PermissionTree tree) {
final var mutable = create();
mutable.setPermissions(tree);
return mutable;
}

default void setPermission(final String permission, final boolean state) {
setPermission(permission, state, false);
}

void setPermission(final String permission, final boolean state, final boolean override);

default void setPermissions(final PermissionTree tree) {
setPermissions(tree, false);
}

default void setPermissions(final PermissionTree tree, final boolean override) {
for (final var entry : tree.getPermissions().entrySet()) {
setPermission(entry.getKey(), entry.getValue(), override);
}
}

void clearPermissions();

void removePermission(final String permission);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,32 +21,32 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.Nullable;

final class PermissionTreeImpl implements PermissionTree {
final class MutablePermissionTreeImpl implements MutablePermissionTree {

private final @Nullable PermissionTreeImpl parent;
private final Map<String, PermissionTreeImpl> children = new HashMap<>();
private final @Nullable MutablePermissionTreeImpl parent;
private final Map<String, MutablePermissionTreeImpl> children = new HashMap<>();
private TriState value = TriState.UNDEFINED;

PermissionTreeImpl() {
MutablePermissionTreeImpl() {
this.parent = null;
}

private PermissionTreeImpl(final @Nullable PermissionTreeImpl parent) {
private MutablePermissionTreeImpl(final @Nullable MutablePermissionTreeImpl parent) {
this.parent = parent;
}

@Override
public TriState getPermission(final String permission) {
if (!PermissionReader.PERMISSION_PATTERN.matcher(permission).matches()) {
throw new IllegalArgumentException("The permission doesn't match the regex: " + permission);
}
checkPermission(permission);
var state = TriState.UNDEFINED;
var node = this;
for (final var part : permission.split("\\.", -1)) {
if (node.children.containsKey("*") && node.children.get("*").value != TriState.UNDEFINED) {
state = node.children.get("*").value;
final var wildcard = node.children.get("*");
if (wildcard != null && wildcard.value != TriState.UNDEFINED) {
state = wildcard.value;
}
node = node.children.get(part);
if (node == null) {
Expand All @@ -58,35 +58,6 @@ public TriState getPermission(final String permission) {
return state;
}

@Override
public void setPermission(final String permission, final TriState state) {
if (!PermissionReader.PERMISSION_PATTERN.matcher(permission).matches()) {
throw new IllegalArgumentException("The permission doesn't match the regex: " + permission);
}
final var parts = permission.split("\\.", -1);
var node = this;
if (state != TriState.UNDEFINED) {
for (final var part : parts) {
final var parent = node;
node = node.children.computeIfAbsent(part, k -> new PermissionTreeImpl(parent));
}
node.value = state;
} else {
for (final var part : parts) {
node = node.children.get(part);
if (node == null) {
return;
}
}
node.value = state;
var index = parts.length - 1;
while (node.parent != null && node.children.isEmpty()) {
node = node.parent;
node.children.remove(parts[index--]);
}
}
}

@Override
public Map<String, Boolean> getPermissions() {
final Map<String, Boolean> permissions = new HashMap<>();
Expand All @@ -101,12 +72,51 @@ public Map<String, Boolean> getPermissions() {
return Collections.unmodifiableMap(permissions);
}

@Override
public void setPermission(final String permission, final boolean state, final boolean override) {
checkPermission(permission);
final var parts = permission.split("\\.", -1);
var node = this;
for (final var part : parts) {
final var parent = node;
node = node.children.computeIfAbsent(part, k -> new MutablePermissionTreeImpl(parent));
}
node.value = TriState.of(state);
if (override) {
node.children.clear();
}
}

@Override
public void removePermission(final String permission) {
checkPermission(permission);
final var parts = permission.split("\\.", -1);
var node = this;
for (final var part : parts) {
node = node.children.get(part);
if (node == null) {
return;
}
}
node.value = TriState.UNDEFINED;
var index = parts.length - 1;
while (node.parent != null && node.children.isEmpty()) {
node = node.parent;
node.children.remove(parts[index--]);
}
}

@Override
public void clearPermissions() {
this.children.clear();
}

@Override
public boolean equals(final @Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof final PermissionTreeImpl that)) {
if (!(o instanceof final MutablePermissionTreeImpl that)) {
return false;
}
if (!this.children.equals(that.children)) {
Expand All @@ -117,8 +127,12 @@ public boolean equals(final @Nullable Object o) {

@Override
public int hashCode() {
int result = this.children.hashCode();
result = 31 * result + this.value.hashCode();
return result;
return Objects.hash(this.children, this.value);
}

private void checkPermission(final String permission) {
if (!PermissionReader.PERMISSION_PATTERN.matcher(permission).matches()) {
throw new IllegalArgumentException("The permission is not valid: " + permission);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,6 @@

public interface PermissionReader {

static PermissionReader empty() {
return EmptyPermissionReader.INSTANCE;
}

/**
* Regex pattern used to validate permission strings.
* <p>
Expand All @@ -43,9 +39,11 @@ static PermissionReader empty() {
* allows you to set a default value for all permissions.
* </blockquote>
*/
String PERMISSION_REGEX = "^(\\*|[\\w\\-]+)(\\.(\\*|[\\w\\-]+))*$";
Pattern PERMISSION_PATTERN = Pattern.compile("^(\\*|\\w+)(\\.(\\*|\\w+))*$");

Pattern PERMISSION_PATTERN = Pattern.compile(PERMISSION_REGEX);
static PermissionReader empty() {
return EmptyPermissionReader.INSTANCE;
}

TriState getPermission(final Player player, final String permission);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,24 @@

public interface PermissionTree {

static PermissionTree create() {
return new PermissionTreeImpl();
}

static PermissionTree empty() {
return EmptyPermissionTree.INSTANCE;
}

static PermissionTree all() {
final var tree = MutablePermissionTree.create();
tree.setPermission("*", true);
return new ImmutablePermissionTree(tree);
}

static PermissionTree immutable(final PermissionTree tree) {
return (tree instanceof ImmutablePermissionTree) ? tree : new ImmutablePermissionTree(tree);
if (tree instanceof ImmutablePermissionTree || tree instanceof EmptyPermissionTree) {
return tree;
}
return new ImmutablePermissionTree(MutablePermissionTree.from(tree));
}

TriState getPermission(final String permission);

void setPermission(final String permission, final TriState state);

Map<String, Boolean> getPermissions();
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ sealed interface Builder permits QueryImpl.Builder {

Builder setInput(final String queryInput);

default Builder setFields(final Field... fields) {
return setFields(Set.of(fields));
}

Builder setFields(final Iterable<Field> fields);

Builder addField(final Field field);
Expand Down
3 changes: 3 additions & 0 deletions distributor-permission-rank/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ dependencies {
compileOnlyApi(projects.distributorCommonApi)
implementation(libs.configurate.core)
implementation(libs.configurate.yaml)
testImplementation(projects.distributorCommon)
testImplementation(libs.slf4j.api)
testImplementation(libs.slf4j.simple)
}

tasks.shadowJar {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,38 @@
import com.xpdustry.distributor.api.permission.PermissionReader;
import com.xpdustry.distributor.api.plugin.AbstractMindustryPlugin;
import com.xpdustry.distributor.api.util.Priority;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

public final class DistributorPermissionRankPlugin extends AbstractMindustryPlugin {

@Override
public void onInit() {
final var services = DistributorProvider.get().getServiceManager();

services.register(this, PermissionReader.class, Priority.HIGH, new RankPermissionReader());
services.register(this, RankSource.class, Priority.LOW, new MindustryRankSource());

final var source = new YamlRankPermissionSource(this.getDirectory().resolve("permissions.yaml"));
final var source = new YamlRankPermissionSource(() -> Files.newBufferedReader(this.getConfigFile()));
this.addListener(source);
services.register(this, RankPermissionSource.class, Priority.LOW, source);

services.register(this, PermissionReader.class, Priority.HIGH, new RankPermissionReader());
services.register(this, RankPermissionSource.class, Priority.NORMAL, source);
services.register(this, RankPermissionSource.class, Priority.LOW, new MindustryRankPermissionSource());

this.getLogger().info("Initialized distributor permission rank plugin");
}

private Path getConfigFile() throws IOException {
final var path = this.getDirectory().resolve("permissions.yaml");
if (Files.notExists(path)) {
try (final var stream = Objects.requireNonNull(this.getClass()
.getClassLoader()
.getResourceAsStream("com/xpdustry/distributor/api/permission/rank/default.yaml"))) {
Files.copy(stream, path);
} catch (final IOException error) {
throw new IOException("Failed to create the default permission file.", error);
}
}
return path;
}
}
Loading

0 comments on commit aa95634

Please sign in to comment.