Compare commits

..
5 Commits
206 changed files with 1068 additions and 7738 deletions

No files matched your search

-5
View File
@@ -1,5 +0,0 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# These are explicitly windows files and should use crlf
*.bat text eol=crlf
+16 -14
View File
@@ -19,24 +19,26 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Setup Java
uses: actions/setup-java@v4
- name: validate gradle wrapper
uses: gradle/wrapper-validation-action@v1
- uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '21'
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v4
uses: gradle/gradle-build-action@v2
- name: Run build and tests with Gradle wrapper
run: ./gradlew test build -PdockerTests
- name: Publish test report
uses: mikepenz/action-junit-report@v5
uses: mikepenz/action-junit-report@v3
if: success() || failure()
with:
report_paths: '**/build/test-results/test/TEST-*.xml'
@@ -44,7 +46,7 @@ jobs:
detailed_summary: true
- name: Upload all artifacts
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: jars
path: |
@@ -59,7 +61,7 @@ jobs:
standalone/loader/build/libs/LuckPerms-Standalone-*.jar
- name: Upload standalone artifact
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v3
with:
name: standalone-binary
path: standalone/loader/build/libs/LuckPerms-Standalone-*.jar
@@ -74,12 +76,12 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Retrieve saved standalone jar artifact
uses: actions/download-artifact@v4
uses: actions/download-artifact@v3
with:
name: standalone-binary
path: standalone/docker/
@@ -88,13 +90,13 @@ jobs:
run: mv standalone/docker/LuckPerms-Standalone-*.jar standalone/docker/luckperms-standalone.jar
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v1
- name: Log in to the Container registry
uses: docker/login-action@v3
uses: docker/login-action@v1
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -106,7 +108,7 @@ jobs:
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: |
@@ -119,7 +121,7 @@ jobs:
type=raw,enable=${{ github.ref == 'refs/heads/master' }},value=${{ env.luckperms_version }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
uses: docker/build-push-action@v2
with:
context: standalone/docker/
platforms: linux/amd64,linux/arm64
+6 -6
View File
@@ -1,5 +1,5 @@
group = 'net.luckperms'
project.version = '5.5'
project.version = '5.4'
jar {
manifest {
@@ -8,8 +8,8 @@ jar {
}
dependencies {
compileOnly 'org.checkerframework:checker-qual:3.49.3'
compileOnly 'org.jetbrains:annotations:26.0.2'
compileOnly 'org.checkerframework:checker-qual:3.21.2'
compileOnly 'org.jetbrains:annotations:23.1.0'
}
// Only used occasionally for deployment - not needed for normal builds.
@@ -24,13 +24,13 @@ if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePass
options.charSet = 'UTF-8'
options.links(
'https://checkerframework.org/api/',
'https://javadoc.io/static/org.jetbrains/annotations/26.0.2/'
'https://javadoc.io/static/org.jetbrains/annotations/23.0.0/'
)
options.addStringOption('Xdoclint:none', '-quiet')
options.addStringOption('-since', '5.0,5.1,5.2,5.3,5.4,5.5')
if (JavaVersion.current() > JavaVersion.VERSION_1_8) {
options.links.add('https://docs.oracle.com/en/java/javase/21/docs/api/')
options.links.add('https://docs.oracle.com/en/java/javase/11/docs/api/')
} else {
options.links.add('https://docs.oracle.com/javase/8/docs/api/')
}
@@ -63,7 +63,7 @@ if (project.hasProperty('sonatypeUsername') && project.hasProperty('sonatypePass
pom {
name = 'LuckPerms API'
description = 'A permissions manager for Minecraft servers.'
description = 'A permissions plugin for Minecraft servers.'
url = 'https://luckperms.net'
licenses {
+1 -1
View File
@@ -1,6 +1,6 @@
<body>
<p>
LuckPerms is a permissions manager for Minecraft servers. It allows server admins to control what
LuckPerms is a permissions plugin for Minecraft servers. It allows server admins to control what
features players can use by creating groups and assigning permissions.
</p>
<h4>Useful Links</h4>
@@ -79,8 +79,7 @@ public final class LuckPermsProvider {
" a) the LuckPerms plugin is not installed or it failed to enable\n" +
" b) the plugin in the stacktrace does not declare a dependency on LuckPerms\n" +
" c) the plugin in the stacktrace is retrieving the API before the plugin 'enable' phase\n" +
" (call the #get method in onEnable, not the constructor!)\n" +
" d) the plugin in the stacktrace is incorrectly 'shading' the LuckPerms API into its jar\n";
" (call the #get method in onEnable, not the constructor!)\n";
NotLoadedException() {
super(MESSAGE);
@@ -63,7 +63,6 @@ public interface DisplayNameNode extends ScopedNode<DisplayNameNode, DisplayName
*
* @param displayName the display name to set
* @return the builder
* @throws IllegalArgumentException if {@code displayName} is empty
*/
static @NonNull Builder builder(@NonNull String displayName) {
return builder().displayName(displayName);
@@ -67,7 +67,6 @@ public interface InheritanceNode extends ScopedNode<InheritanceNode, Inheritance
*
* @param group the group to set
* @return the builder
* @throws IllegalArgumentException if {@code group} is not a valid group name
*/
static @NonNull Builder builder(@NonNull String group) {
return builder().group(group);
@@ -71,7 +71,6 @@ public interface MetaNode extends ScopedNode<MetaNode, MetaNode.Builder> {
* @param key the meta key to set
* @param value the meta value to set
* @return the builder
* @throws IllegalArgumentException if {@code key} is empty
*/
static @NonNull Builder builder(@NonNull String key, @NonNull String value) {
return builder().key(key).value(value);
@@ -88,7 +88,6 @@ public interface PermissionNode extends ScopedNode<PermissionNode, PermissionNod
*
* @param permission the permission to set
* @return the builder
* @throws IllegalArgumentException if {@code permission} is empty
*/
static @NonNull Builder builder(@NonNull String permission) {
return builder().permission(permission);
@@ -75,7 +75,6 @@ public interface RegexPermissionNode extends ScopedNode<RegexPermissionNode, Reg
*
* @param pattern the pattern to set
* @return the builder
* @throws IllegalArgumentException if {@code pattern} is empty
*/
static @NonNull Builder builder(@NonNull String pattern) {
return builder().pattern(pattern);
@@ -76,7 +76,6 @@ public interface Platform {
NUKKIT("Nukkit"),
VELOCITY("Velocity"),
FABRIC("Fabric"),
NEOFORGE("NeoForge"),
FORGE("Forge"),
STANDALONE("Standalone");
@@ -48,7 +48,7 @@ public interface DataTypeFilterFunction {
/**
* Creates a {@link DataTypeFilterFunction} that always returns the given
* {@code predicate} (commonly one of the values in {@link DataTypeFilter}).
* {@code predicate}.
*
* @param predicate the predicate
* @return the data type filter function
+2 -2
View File
@@ -13,7 +13,7 @@ subprojects {
apply plugin: 'org.cadixdev.licenser'
group = 'me.lucko.luckperms'
version = '5.5-SNAPSHOT'
version = '5.4-SNAPSHOT'
sourceCompatibility = 1.8
targetCompatibility = 1.8
@@ -52,7 +52,7 @@ subprojects {
}
project.ext.majorVersion = '5'
project.ext.minorVersion = '5'
project.ext.minorVersion = '4'
project.ext.patchVersion = determinePatchVersion()
project.ext.apiVersion = project.ext.majorVersion + '.' + project.ext.minorVersion
project.ext.fullVersion = project.ext.apiVersion + '.' + project.ext.patchVersion
+1 -1
View File
@@ -11,7 +11,7 @@ dependencies {
compileOnly project(':common:loader-utils')
compileOnly 'com.destroystokyo.paper:paper-api:1.15.2-R0.1-SNAPSHOT'
compileOnly('net.kyori:adventure-platform-bukkit:4.4.0') {
compileOnly('net.kyori:adventure-platform-bukkit:4.3.3') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'adventure-nbt')
@@ -25,46 +25,85 @@
package me.lucko.luckperms.bukkit.context;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bukkit.LPBukkitPlugin;
import me.lucko.luckperms.bukkit.inject.permissible.LuckPermsPermissible;
import me.lucko.luckperms.bukkit.inject.permissible.PermissibleInjector;
import me.lucko.luckperms.common.context.manager.DetachedContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.cache.LoadingMap;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.OptionKey;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.entity.Player;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BukkitContextManager extends DetachedContextManager<Player, Player> {
public class BukkitContextManager extends ContextManager<Player, Player> {
public static final OptionKey<Boolean> OP_OPTION = OptionKey.of("op", Boolean.class);
// cache the creation of ContextsCache instances for online players with no expiry
private final LoadingMap<Player, QueryOptionsCache<Player>> onlineSubjectCaches = LoadingMap.of(key -> new QueryOptionsCache<>(key, this));
// cache the creation of ContextsCache instances for offline players with a 1m expiry
private final LoadingCache<Player, QueryOptionsCache<Player>> offlineSubjectCaches = CaffeineFactory.newBuilder()
.expireAfterAccess(1, TimeUnit.MINUTES)
.build(key -> {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(key);
if (cache != null) {
return cache;
}
return new QueryOptionsCache<>(key, this);
});
public BukkitContextManager(LPBukkitPlugin plugin) {
super(plugin, Player.class, Player.class);
}
public void onPlayerQuit(Player player) {
this.onlineSubjectCaches.remove(player);
}
@Override
public UUID getUniqueId(Player player) {
return player.getUniqueId();
}
@Override
public @Nullable QueryOptionsSupplier getQueryOptionsSupplier(Player subject) {
Objects.requireNonNull(subject, "subject");
LuckPermsPermissible permissible = PermissibleInjector.get(subject);
if (permissible != null) {
return permissible.getQueryOptionsSupplier();
public QueryOptionsCache<Player> getCacheFor(Player subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
if (subject.isOnline()) {
return this.onlineSubjectCaches.get(subject);
} else {
return this.offlineSubjectCaches.get(subject);
}
return null;
}
@Override
public void customizeQueryOptions(Player subject, QueryOptions.Builder builder) {
if (subject.isOp()) {
builder.option(OP_OPTION, true);
protected void invalidateCache(Player subject) {
QueryOptionsCache<Player> cache = this.onlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
cache = this.offlineSubjectCaches.getIfPresent(subject);
if (cache != null) {
cache.invalidate();
}
}
@Override
public QueryOptions formQueryOptions(Player subject, ImmutableContextSet contextSet) {
QueryOptions.Builder queryOptions = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder();
if (subject.isOp()) {
queryOptions.option(OP_OPTION, true);
}
return queryOptions.context(contextSet).build();
}
}
@@ -32,7 +32,7 @@ import me.lucko.luckperms.bukkit.calculator.OpProcessor;
import me.lucko.luckperms.bukkit.calculator.PermissionMapProcessor;
import me.lucko.luckperms.common.cacheddata.result.TristateResult;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.context.manager.QueryOptionsCache;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.verbose.event.CheckOrigin;
import net.luckperms.api.query.QueryOptions;
@@ -68,7 +68,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* "Hot" method calls, (namely #hasPermission) are significantly faster than the base implementation.
*
* This class is **thread safe**. This means that when LuckPerms is installed on the server,
* it is safe to call Player#hasPermission asynchronously.
* is is safe to call Player#hasPermission asynchronously.
*/
public class LuckPermsPermissible extends PermissibleBase {
@@ -93,7 +93,7 @@ public class LuckPermsPermissible extends PermissibleBase {
private final LPBukkitPlugin plugin;
// caches context lookups for the player
private final QueryOptionsSupplier queryOptionsSupplier;
private final QueryOptionsCache<Player> queryOptionsSupplier;
// the players previous permissible. (the one they had before this one was injected)
private PermissibleBase oldPermissible = null;
@@ -110,7 +110,7 @@ public class LuckPermsPermissible extends PermissibleBase {
this.user = Objects.requireNonNull(user, "user");
this.player = Objects.requireNonNull(player, "player");
this.plugin = Objects.requireNonNull(plugin, "plugin");
this.queryOptionsSupplier = plugin.getContextManager().createQueryOptionsSupplier(player);
this.queryOptionsSupplier = plugin.getContextManager().getCacheFor(player);
injectFakeAttachmentsList();
}
@@ -293,7 +293,7 @@ public class LuckPermsPermissible extends PermissibleBase {
// the query options cache when op status changes.
// (#invalidate is a fast call)
if (this.queryOptionsSupplier != null) { // this method is called by the super class constructor, before this class has fully initialised
this.queryOptionsSupplier.invalidateCache();
this.queryOptionsSupplier.invalidate();
}
// but we don't need to do anything else in this method, unlike the CB impl.
@@ -316,10 +316,6 @@ public class LuckPermsPermissible extends PermissibleBase {
return this.plugin;
}
public QueryOptionsSupplier getQueryOptionsSupplier() {
return this.queryOptionsSupplier;
}
PermissibleBase getOldPermissible() {
return this.oldPermissible;
}
@@ -413,7 +409,9 @@ public class LuckPermsPermissible extends PermissibleBase {
@Override public PermissionAttachment remove(int index) { throw new UnsupportedOperationException(); }
@Override public int indexOf(Object o) { throw new UnsupportedOperationException(); }
@Override public int lastIndexOf(Object o) { throw new UnsupportedOperationException(); }
@Override public @NonNull ListIterator<PermissionAttachment> listIterator(int index) { throw new UnsupportedOperationException(); }
@Override public @NonNull List<PermissionAttachment> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }
@Override
public @NonNull ListIterator<PermissionAttachment> listIterator(int index) { throw new UnsupportedOperationException(); }
@Override
public @NonNull List<PermissionAttachment> subList(int fromIndex, int toIndex) { throw new UnsupportedOperationException(); }
}
}
@@ -30,7 +30,6 @@ import me.lucko.luckperms.common.plugin.logging.PluginLogger;
import org.bukkit.entity.Player;
import org.bukkit.permissions.PermissibleBase;
import org.bukkit.permissions.PermissionAttachment;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.lang.reflect.Field;
import java.util.List;
@@ -162,19 +161,6 @@ public final class PermissibleInjector {
}
}
public static @Nullable LuckPermsPermissible get(Player player) {
PermissibleBase permissibleBase;
try {
permissibleBase = (PermissibleBase) HUMAN_ENTITY_PERMISSIBLE_FIELD.get(player);
} catch (IllegalAccessException e) {
return null;
}
if (permissibleBase instanceof LuckPermsPermissible) {
return (LuckPermsPermissible) permissibleBase;
}
return null;
}
public static void checkInjected(Player player, PluginLogger logger) {
PermissibleBase permissibleBase;
try {
@@ -253,6 +253,9 @@ public class BukkitConnectionListener extends AbstractConnectionListener impleme
if (this.plugin.getConfiguration().get(ConfigKeys.AUTO_OP)) {
player.setOp(false);
}
// remove their contexts cache
this.plugin.getContextManager().onPlayerQuit(player);
}, 1L);
}
@@ -36,26 +36,12 @@ import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.event.server.RemoteServerCommandEvent;
import org.bukkit.event.server.ServerCommandEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginDescriptionFile;
import java.util.regex.Pattern;
public class BukkitPlatformListener implements Listener {
private static final Pattern OP_COMMAND_PATTERN = Pattern.compile("^/?(\\w+:)?(deop|op)( .*)?$", Pattern.CASE_INSENSITIVE);
private static final boolean PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED;
static {
boolean supported = false;
try {
PluginDescriptionFile.class.getMethod("getProvides");
supported = true;
} catch (NoSuchMethodException ignored) {
}
PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED = supported;
}
private final LPBukkitPlugin plugin;
public BukkitPlatformListener(LPBukkitPlugin plugin) {
@@ -94,11 +80,7 @@ public class BukkitPlatformListener implements Listener {
@EventHandler
public void onPluginEnable(PluginEnableEvent e) {
Plugin p = e.getPlugin();
boolean shouldHook = p.getName().equalsIgnoreCase("Vault") ||
(PLUGIN_DESCRIPTION_FILE_PROVIDES_SUPPORTED && p.getDescription().getProvides().contains("Vault"));
if (shouldHook) {
if (e.getPlugin().getName().equalsIgnoreCase("Vault")) {
this.plugin.tryVaultHook(true);
}
}
-30
View File
@@ -719,33 +719,3 @@ register-command-list-data: true
# If LuckPerms should attempt to resolve Vanilla command target selectors for LP commands.
# See here for more info: https://minecraft.wiki/w/Target_selectors
resolve-command-selectors: false
# If the plugin should run in "read-only" mode for commands.
#
# In this mode, players or the console will only be able to execute commands that read or view
# data, and will not be able to execute any LP commands that modify data.
#
# If enabling read-only mode for just players, be aware that some other plugins may allow players
# to execute commands as the console, allowing them to run LP commands indirectly.
#
# Note: This does not affect interactions with LuckPerms via the API.
commands-read-only-mode:
players: false
console: false
# If LuckPerms commands should be disabled. When true, this will prevent all LP commands from being
# executed. In a sense this is a more extreme version of read-only mode (see above).
#
# LuckPerms will still act as the permission manager, but server administrators will be unable to
# make changes via commands.
#
# If disabling commands just for players, be aware that some other plugins may allow players to
# execute commands as the console, allowing them to run commands indirectly.
#
# If commands are disabled for both players and the console, LuckPerms will not attempt to register
# a command with the server at all & in a sense will be invisible to both players and admins.
#
# Note: This does not affect interactions with LuckPerms via the API.
disable-luckperms-commands:
players: false
console: false
+1 -1
View File
@@ -7,7 +7,7 @@ dependencies {
compileOnly project(':common:loader-utils')
compileOnly 'net.md-5:bungeecord-api:1.16-R0.4'
compileOnly('net.kyori:adventure-platform-bungeecord:4.4.0') {
compileOnly('net.kyori:adventure-platform-bungeecord:4.3.3') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'adventure-nbt')
@@ -25,13 +25,25 @@
package me.lucko.luckperms.bungee.context;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.bungee.LPBungeePlugin;
import me.lucko.luckperms.common.context.manager.SimpleContextManager;
import me.lucko.luckperms.common.context.manager.ContextManager;
import me.lucko.luckperms.common.context.manager.InlineQueryOptionsSupplier;
import me.lucko.luckperms.common.context.manager.QueryOptionsSupplier;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.query.QueryOptions;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class BungeeContextManager extends ContextManager<ProxiedPlayer, ProxiedPlayer> {
private final LoadingCache<ProxiedPlayer, QueryOptions> contextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
public class BungeeContextManager extends SimpleContextManager<ProxiedPlayer, ProxiedPlayer> {
public BungeeContextManager(LPBungeePlugin plugin) {
super(plugin, ProxiedPlayer.class, ProxiedPlayer.class);
}
@@ -40,4 +52,35 @@ public class BungeeContextManager extends SimpleContextManager<ProxiedPlayer, Pr
public UUID getUniqueId(ProxiedPlayer player) {
return player.getUniqueId();
}
@Override
public QueryOptionsSupplier getCacheFor(ProxiedPlayer subject) {
if (subject == null) {
throw new NullPointerException("subject");
}
return new InlineQueryOptionsSupplier<>(subject, this.contextsCache);
}
// override getContext, getQueryOptions and invalidateCache to skip the QueryOptionsSupplier
@Override
public ImmutableContextSet getContext(ProxiedPlayer subject) {
return getQueryOptions(subject).context();
}
@Override
public QueryOptions getQueryOptions(ProxiedPlayer subject) {
return this.contextsCache.get(subject);
}
@Override
protected void invalidateCache(ProxiedPlayer subject) {
this.contextsCache.invalidate(subject);
}
@Override
public QueryOptions formQueryOptions(ProxiedPlayer subject, ImmutableContextSet contextSet) {
return formQueryOptions(contextSet);
}
}
-30
View File
@@ -611,33 +611,3 @@ disable-bulkupdate: false
#
# - When this happens, the plugin will set their primary group back to default.
prevent-primary-group-removal: false
# If the plugin should run in "read-only" mode for commands.
#
# In this mode, players or the console will only be able to execute commands that read or view
# data, and will not be able to execute any LP commands that modify data.
#
# If enabling read-only mode for just players, be aware that some other plugins may allow players
# to execute commands as the console, allowing them to run LP commands indirectly.
#
# Note: This does not affect interactions with LuckPerms via the API.
commands-read-only-mode:
players: false
console: false
# If LuckPerms commands should be disabled. When true, this will prevent all LP commands from being
# executed. In a sense this is a more extreme version of read-only mode (see above).
#
# LuckPerms will still act as the permission manager, but server administrators will be unable to
# make changes via commands.
#
# If disabling commands just for players, be aware that some other plugins may allow players to
# execute commands as the console, allowing them to run commands indirectly.
#
# If commands are disabled for both players and the console, LuckPerms will not attempt to register
# a command with the server at all & in a sense will be invisible to both players and admins.
#
# Note: This does not affect interactions with LuckPerms via the API.
disable-luckperms-commands:
players: false
console: false
+6 -9
View File
@@ -16,11 +16,10 @@ jacocoTestReport {
}
dependencies {
testImplementation 'org.slf4j:slf4j-simple:1.7.36'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter-engine:5.9.1'
testImplementation 'org.junit.jupiter:junit-jupiter-params:5.9.1'
testImplementation 'org.testcontainers:junit-jupiter:1.20.4'
testImplementation "org.testcontainers:junit-jupiter:1.19.8"
testImplementation 'org.mockito:mockito-core:5.11.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
testImplementation 'com.h2database:h2:2.1.214'
@@ -28,7 +27,6 @@ dependencies {
testImplementation 'org.spongepowered:configurate-yaml:3.7.2'
testImplementation 'org.spongepowered:configurate-hocon:3.7.2'
testImplementation 'me.lucko.configurate:configurate-toml:3.7'
testImplementation 'net.luckperms:rest-api-java-client:0.1-SNAPSHOT'
api project(':api')
api 'org.checkerframework:checker-qual:3.12.0'
@@ -39,29 +37,29 @@ dependencies {
compileOnly 'org.slf4j:slf4j-api:1.7.30'
compileOnly 'org.apache.logging.log4j:log4j-api:2.14.0'
api('net.kyori:adventure-api:4.21.0') {
api('net.kyori:adventure-api:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'checker-qual')
exclude(module: 'annotations')
}
api('net.kyori:adventure-text-serializer-gson:4.21.0') {
api('net.kyori:adventure-text-serializer-gson:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
exclude(module: 'gson')
}
api('net.kyori:adventure-text-serializer-legacy:4.21.0') {
api('net.kyori:adventure-text-serializer-legacy:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api('net.kyori:adventure-text-serializer-plain:4.21.0') {
api('net.kyori:adventure-text-serializer-plain:4.11.0') {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
api("net.kyori:adventure-text-minimessage:4.21.0") {
api("net.kyori:adventure-text-minimessage:4.11.0") {
exclude(module: 'adventure-bom')
exclude(module: 'adventure-api')
}
@@ -101,5 +99,4 @@ dependencies {
compileOnly 'org.mongodb:mongodb-driver-legacy:4.5.0'
compileOnly 'org.postgresql:postgresql:42.6.0'
compileOnly 'org.yaml:snakeyaml:1.28'
compileOnly 'net.luckperms:rest-api-java-client:0.1-SNAPSHOT'
}
@@ -26,8 +26,8 @@
package me.lucko.luckperms.common.api.implementation;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.cacheddata.metastack.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
import net.luckperms.api.metastacking.MetaStackDefinition;
@@ -193,7 +193,7 @@ public abstract class AbstractCachedDataManager implements CachedDataManager {
}
public void cleanup() {
this.cache.values().removeIf(value -> !((UsageTracked) value).usedInTheLast(2, TimeUnit.MINUTES));
this.cache.values().removeIf(value -> ((UsageTracked) value).usedSince(TimeUnit.MINUTES.toMillis(2)));
}
@Override
@@ -25,20 +25,14 @@
package me.lucko.luckperms.common.cacheddata;
import com.google.common.annotations.VisibleForTesting;
import java.util.concurrent.TimeUnit;
public abstract class UsageTracked {
@VisibleForTesting
protected long lastUsed = System.currentTimeMillis();
private long lastUsed = System.currentTimeMillis();
public void recordUsage() {
this.lastUsed = System.currentTimeMillis();
}
public boolean usedInTheLast(long duration, TimeUnit unit) {
return this.lastUsed > System.currentTimeMillis() - unit.toMillis(duration);
public boolean usedSince(long duration) {
return this.lastUsed > System.currentTimeMillis() - duration;
}
}
@@ -1,57 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import org.checkerframework.checker.nullness.qual.Nullable;
public abstract class AbstractResult<T, N extends Node, S extends AbstractResult<T, N, S>> implements Result<T, N> {
/** The node that caused the result */
protected final N node;
/** A reference to another result that this one overrides */
protected S overriddenResult;
public AbstractResult(N node, S overriddenResult) {
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
public final @Nullable N node() {
return this.node;
}
public final @Nullable S overriddenResult() {
return this.overriddenResult;
}
public final void setOverriddenResult(S overriddenResult) {
this.overriddenResult = overriddenResult;
}
}
@@ -25,23 +25,30 @@
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.WeightNode;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the result of an integer meta lookup
*
* @param <N> the node type
*/
public final class IntegerResult<N extends Node> extends AbstractResult<Integer, N, IntegerResult<N>> {
public final class IntegerResult<N extends Node> implements Result<Integer, N> {
/** The result */
private final int result;
/** The node that caused the result */
private final N node;
/** A reference to another result that this one overrides */
private IntegerResult<N> overriddenResult;
public IntegerResult(int result, N node, IntegerResult<N> overriddenResult) {
super(node, overriddenResult);
this.result = result;
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
@@ -66,6 +73,19 @@ public final class IntegerResult<N extends Node> extends AbstractResult<Integer,
}
}
@Override
public @Nullable N node() {
return this.node;
}
public @Nullable IntegerResult<N> overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(IntegerResult<N> overriddenResult) {
this.overriddenResult = overriddenResult;
}
public boolean isNull() {
return this == NULL_RESULT;
}
@@ -25,6 +25,7 @@
package me.lucko.luckperms.common.cacheddata.result;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.types.ChatMetaNode;
import net.luckperms.api.node.types.MetaNode;
@@ -35,14 +36,19 @@ import org.checkerframework.checker.nullness.qual.Nullable;
*
* @param <N> the node type
*/
public final class StringResult<N extends Node> extends AbstractResult<String, N, StringResult<N>> {
public final class StringResult<N extends Node> implements Result<String, N> {
/** The result, nullable */
private final String result;
/** The node that caused the result */
private final N node;
/** A reference to another result that this one overrides */
private StringResult<N> overriddenResult;
public StringResult(String result, N node, StringResult<N> overriddenResult) {
super(node, overriddenResult);
this.result = result;
this.node = node;
this.overriddenResult = overriddenResult;
}
@Override
@@ -50,6 +56,19 @@ public final class StringResult<N extends Node> extends AbstractResult<String, N
return this.result;
}
@Override
public @Nullable N node() {
return this.node;
}
public @Nullable StringResult<N> overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(StringResult<N> overriddenResult) {
this.overriddenResult = overriddenResult;
}
public StringResult<N> copy() {
return new StringResult<>(this.result, this.node, this.overriddenResult);
}
@@ -27,6 +27,7 @@ package me.lucko.luckperms.common.cacheddata.result;
import me.lucko.luckperms.common.calculator.PermissionCalculator;
import me.lucko.luckperms.common.calculator.processor.PermissionProcessor;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
import net.luckperms.api.util.Tristate;
import org.checkerframework.checker.nullness.qual.NonNull;
@@ -35,16 +36,20 @@ import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Represents the result of a {@link PermissionCalculator} lookup.
*/
public final class TristateResult extends AbstractResult<Tristate, Node, TristateResult> {
public final class TristateResult implements Result<Tristate, Node> {
/** The result */
private final Tristate result;
/** The node that caused the result */
private final Node node;
/** The permission processor that provided the result */
private final Class<? extends PermissionProcessor> processorClass;
/** A reference to another result that this one overrides */
private TristateResult overriddenResult;
private TristateResult(Tristate result, Node node, Class<? extends PermissionProcessor> processorClass) {
super(node, null);
this.result = result;
this.node = node;
this.processorClass = processorClass;
}
@@ -53,6 +58,11 @@ public final class TristateResult extends AbstractResult<Tristate, Node, Tristat
return this.result;
}
@Override
public @Nullable Node node() {
return this.node;
}
public @Nullable Class<? extends PermissionProcessor> processorClass() {
return this.processorClass;
}
@@ -69,6 +79,14 @@ public final class TristateResult extends AbstractResult<Tristate, Node, Tristat
}
}
public @Nullable TristateResult overriddenResult() {
return this.overriddenResult;
}
public void setOverriddenResult(TristateResult overriddenResult) {
this.overriddenResult = overriddenResult;
}
@Override
public String toString() {
return "TristateResult(" +
@@ -79,7 +79,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
@@ -102,7 +101,7 @@ public class CommandManager {
.build()
);
private final AtomicBoolean executingCommand = new AtomicBoolean(false);
private final Set<UUID> playerRateLimit = ExpiringSet.newExpiringSet(500, TimeUnit.MILLISECONDS);
private final ExpiringSet<UUID> playerRateLimit = new ExpiringSet<>(500, TimeUnit.MILLISECONDS);
private final TabCompletions tabCompletions;
private final Map<String, Command<?>> mainCommands;
@@ -160,15 +159,6 @@ public class CommandManager {
return CompletableFuture.completedFuture(null);
}
boolean commandsDisabled = sender.isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
if (commandsDisabled) {
Message.COMMANDS_DISABLED.send(sender);
return CompletableFuture.completedFuture(null);
}
SchedulerAdapter scheduler = this.plugin.getBootstrap().getScheduler();
List<String> argsCopy = new ArrayList<>(args);
@@ -295,14 +285,6 @@ public class CommandManager {
}
public List<String> tabCompleteCommand(Sender sender, List<String> arguments) {
boolean commandsDisabled = sender.isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
if (commandsDisabled) {
return Collections.emptyList();
}
applyConvenienceAliases(arguments, false);
final List<Command<?>> mains = this.mainCommands.values().stream()
@@ -31,7 +31,6 @@ import me.lucko.luckperms.common.command.spec.CommandSpec;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -67,7 +66,7 @@ public abstract class ChildCommand<T> extends Command<T> {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.append(Component.join(Component.space(), argUsages))
.build();
}
@@ -35,7 +35,6 @@ import me.lucko.luckperms.common.model.PermissionHolder;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -115,7 +114,7 @@ public abstract class GenericChildCommand {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.append(Component.join(Component.space(), argUsages))
.build();
}
@@ -33,7 +33,6 @@ import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.sender.Sender;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -71,7 +70,7 @@ public abstract class SingleCommand extends Command<Void> {
.collect(Collectors.toList());
builder.append(Component.text(" - ", NamedTextColor.DARK_AQUA))
.append(Component.join(JoinConfiguration.separator(Component.space()), argUsages))
.append(Component.join(Component.space(), argUsages))
.build();
}
@@ -32,143 +32,143 @@ import me.lucko.luckperms.common.sender.Sender;
*/
public enum CommandPermission {
SYNC("sync", Type.NONE, true),
INFO("info", Type.NONE, true),
EDITOR("editor", Type.NONE, true),
VERBOSE("verbose", Type.NONE, true),
VERBOSE_COMMAND_OTHERS("verbose.command.others", Type.NONE, false),
TREE("tree", Type.NONE, true),
SEARCH("search", Type.NONE, true),
IMPORT("import", Type.NONE, false),
EXPORT("export", Type.NONE, true),
RELOAD_CONFIG("reloadconfig", Type.NONE, true),
BULK_UPDATE("bulkupdate", Type.NONE, false),
APPLY_EDITS("applyedits", Type.NONE, false),
TRUST_EDITOR("trusteditor", Type.NONE, false),
TRANSLATIONS("translations", Type.NONE, true),
SYNC("sync", Type.NONE),
INFO("info", Type.NONE),
EDITOR("editor", Type.NONE),
VERBOSE("verbose", Type.NONE),
VERBOSE_COMMAND_OTHERS("verbose.command.others", Type.NONE),
TREE("tree", Type.NONE),
SEARCH("search", Type.NONE),
IMPORT("import", Type.NONE),
EXPORT("export", Type.NONE),
RELOAD_CONFIG("reloadconfig", Type.NONE),
BULK_UPDATE("bulkupdate", Type.NONE),
APPLY_EDITS("applyedits", Type.NONE),
TRUST_EDITOR("trusteditor", Type.NONE),
TRANSLATIONS("translations", Type.NONE),
CREATE_GROUP("creategroup", Type.NONE, false),
DELETE_GROUP("deletegroup", Type.NONE, false),
LIST_GROUPS("listgroups", Type.NONE, true),
CREATE_GROUP("creategroup", Type.NONE),
DELETE_GROUP("deletegroup", Type.NONE),
LIST_GROUPS("listgroups", Type.NONE),
CREATE_TRACK("createtrack", Type.NONE, false),
DELETE_TRACK("deletetrack", Type.NONE, false),
LIST_TRACKS("listtracks", Type.NONE, true),
CREATE_TRACK("createtrack", Type.NONE),
DELETE_TRACK("deletetrack", Type.NONE),
LIST_TRACKS("listtracks", Type.NONE),
USER_INFO("info", Type.USER, true),
USER_PERM_INFO("permission.info", Type.USER, true),
USER_PERM_SET("permission.set", Type.USER, false),
USER_PERM_UNSET("permission.unset", Type.USER, false),
USER_PERM_SET_TEMP("permission.settemp", Type.USER, false),
USER_PERM_UNSET_TEMP("permission.unsettemp", Type.USER, false),
USER_PERM_CHECK("permission.check", Type.USER, true),
USER_PERM_CLEAR("permission.clear", Type.USER, false),
USER_PARENT_INFO("parent.info", Type.USER, true),
USER_PARENT_SET("parent.set", Type.USER, false),
USER_PARENT_SET_TRACK("parent.settrack", Type.USER, false),
USER_PARENT_ADD("parent.add", Type.USER, false),
USER_PARENT_REMOVE("parent.remove", Type.USER, false),
USER_PARENT_ADD_TEMP("parent.addtemp", Type.USER, false),
USER_PARENT_REMOVE_TEMP("parent.removetemp", Type.USER, false),
USER_PARENT_CLEAR("parent.clear", Type.USER, false),
USER_PARENT_CLEAR_TRACK("parent.cleartrack", Type.USER, false),
USER_PARENT_SWITCHPRIMARYGROUP("parent.switchprimarygroup", Type.USER, false),
USER_META_INFO("meta.info", Type.USER, true),
USER_META_SET("meta.set", Type.USER, false),
USER_META_UNSET("meta.unset", Type.USER, false),
USER_META_SET_TEMP("meta.settemp", Type.USER, false),
USER_META_UNSET_TEMP("meta.unsettemp", Type.USER, false),
USER_META_ADD_PREFIX("meta.addprefix", Type.USER, false),
USER_META_ADD_SUFFIX("meta.addsuffix", Type.USER, false),
USER_META_SET_PREFIX("meta.setprefix", Type.USER, false),
USER_META_SET_SUFFIX("meta.setsuffix", Type.USER, false),
USER_META_REMOVE_PREFIX("meta.removeprefix", Type.USER, false),
USER_META_REMOVE_SUFFIX("meta.removesuffix", Type.USER, false),
USER_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.USER, false),
USER_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.USER, false),
USER_META_SET_TEMP_PREFIX("meta.settempprefix", Type.USER, false),
USER_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.USER, false),
USER_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.USER, false),
USER_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.USER, false),
USER_META_CLEAR("meta.clear", Type.USER, false),
USER_EDITOR("editor", Type.USER, true),
USER_SHOW_TRACKS("showtracks", Type.USER, true),
USER_PROMOTE("promote", Type.USER, false),
USER_DEMOTE("demote", Type.USER, false),
USER_CLEAR("clear", Type.USER, false),
USER_CLONE("clone", Type.USER, false),
USER_INFO("info", Type.USER),
USER_PERM_INFO("permission.info", Type.USER),
USER_PERM_SET("permission.set", Type.USER),
USER_PERM_UNSET("permission.unset", Type.USER),
USER_PERM_SET_TEMP("permission.settemp", Type.USER),
USER_PERM_UNSET_TEMP("permission.unsettemp", Type.USER),
USER_PERM_CHECK("permission.check", Type.USER),
USER_PERM_CLEAR("permission.clear", Type.USER),
USER_PARENT_INFO("parent.info", Type.USER),
USER_PARENT_SET("parent.set", Type.USER),
USER_PARENT_SET_TRACK("parent.settrack", Type.USER),
USER_PARENT_ADD("parent.add", Type.USER),
USER_PARENT_REMOVE("parent.remove", Type.USER),
USER_PARENT_ADD_TEMP("parent.addtemp", Type.USER),
USER_PARENT_REMOVE_TEMP("parent.removetemp", Type.USER),
USER_PARENT_CLEAR("parent.clear", Type.USER),
USER_PARENT_CLEAR_TRACK("parent.cleartrack", Type.USER),
USER_PARENT_SWITCHPRIMARYGROUP("parent.switchprimarygroup", Type.USER),
USER_META_INFO("meta.info", Type.USER),
USER_META_SET("meta.set", Type.USER),
USER_META_UNSET("meta.unset", Type.USER),
USER_META_SET_TEMP("meta.settemp", Type.USER),
USER_META_UNSET_TEMP("meta.unsettemp", Type.USER),
USER_META_ADD_PREFIX("meta.addprefix", Type.USER),
USER_META_ADD_SUFFIX("meta.addsuffix", Type.USER),
USER_META_SET_PREFIX("meta.setprefix", Type.USER),
USER_META_SET_SUFFIX("meta.setsuffix", Type.USER),
USER_META_REMOVE_PREFIX("meta.removeprefix", Type.USER),
USER_META_REMOVE_SUFFIX("meta.removesuffix", Type.USER),
USER_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.USER),
USER_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.USER),
USER_META_SET_TEMP_PREFIX("meta.settempprefix", Type.USER),
USER_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.USER),
USER_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.USER),
USER_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.USER),
USER_META_CLEAR("meta.clear", Type.USER),
USER_EDITOR("editor", Type.USER),
USER_SHOW_TRACKS("showtracks", Type.USER),
USER_PROMOTE("promote", Type.USER),
USER_DEMOTE("demote", Type.USER),
USER_CLEAR("clear", Type.USER),
USER_CLONE("clone", Type.USER),
GROUP_INFO("info", Type.GROUP, true),
GROUP_PERM_INFO("permission.info", Type.GROUP, true),
GROUP_PERM_SET("permission.set", Type.GROUP, false),
GROUP_PERM_UNSET("permission.unset", Type.GROUP, false),
GROUP_PERM_SET_TEMP("permission.settemp", Type.GROUP, false),
GROUP_PERM_UNSET_TEMP("permission.unsettemp", Type.GROUP, false),
GROUP_PERM_CHECK("permission.check", Type.GROUP, true),
GROUP_PERM_CLEAR("permission.clear", Type.GROUP, false),
GROUP_PARENT_INFO("parent.info", Type.GROUP, true),
GROUP_PARENT_SET("parent.set", Type.GROUP, false),
GROUP_PARENT_SET_TRACK("parent.settrack", Type.GROUP, false),
GROUP_PARENT_ADD("parent.add", Type.GROUP, false),
GROUP_PARENT_REMOVE("parent.remove", Type.GROUP, false),
GROUP_PARENT_ADD_TEMP("parent.addtemp", Type.GROUP, false),
GROUP_PARENT_REMOVE_TEMP("parent.removetemp", Type.GROUP, false),
GROUP_PARENT_CLEAR("parent.clear", Type.GROUP, false),
GROUP_PARENT_CLEAR_TRACK("parent.cleartrack", Type.GROUP, false),
GROUP_META_INFO("meta.info", Type.GROUP, true),
GROUP_META_SET("meta.set", Type.GROUP, false),
GROUP_META_UNSET("meta.unset", Type.GROUP, false),
GROUP_META_SET_TEMP("meta.settemp", Type.GROUP, false),
GROUP_META_UNSET_TEMP("meta.unsettemp", Type.GROUP, false),
GROUP_META_ADD_PREFIX("meta.addprefix", Type.GROUP, false),
GROUP_META_ADD_SUFFIX("meta.addsuffix", Type.GROUP, false),
GROUP_META_SET_PREFIX("meta.setprefix", Type.GROUP, false),
GROUP_META_SET_SUFFIX("meta.setsuffix", Type.GROUP, false),
GROUP_META_REMOVE_PREFIX("meta.removeprefix", Type.GROUP, false),
GROUP_META_REMOVE_SUFFIX("meta.removesuffix", Type.GROUP, false),
GROUP_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.GROUP, false),
GROUP_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.GROUP, false),
GROUP_META_SET_TEMP_PREFIX("meta.settempprefix", Type.GROUP, false),
GROUP_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.GROUP, false),
GROUP_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.GROUP, false),
GROUP_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.GROUP, false),
GROUP_META_CLEAR("meta.clear", Type.GROUP, false),
GROUP_EDITOR("editor", Type.GROUP, true),
GROUP_LIST_MEMBERS("listmembers", Type.GROUP, true),
GROUP_SHOW_TRACKS("showtracks", Type.GROUP, true),
GROUP_SET_WEIGHT("setweight", Type.GROUP, false),
GROUP_SET_DISPLAY_NAME("setdisplayname", Type.GROUP, false),
GROUP_CLEAR("clear", Type.GROUP, false),
GROUP_RENAME("rename", Type.GROUP, false),
GROUP_CLONE("clone", Type.GROUP, false),
GROUP_INFO("info", Type.GROUP),
GROUP_PERM_INFO("permission.info", Type.GROUP),
GROUP_PERM_SET("permission.set", Type.GROUP),
GROUP_PERM_UNSET("permission.unset", Type.GROUP),
GROUP_PERM_SET_TEMP("permission.settemp", Type.GROUP),
GROUP_PERM_UNSET_TEMP("permission.unsettemp", Type.GROUP),
GROUP_PERM_CHECK("permission.check", Type.GROUP),
GROUP_PERM_CLEAR("permission.clear", Type.GROUP),
GROUP_PARENT_INFO("parent.info", Type.GROUP),
GROUP_PARENT_SET("parent.set", Type.GROUP),
GROUP_PARENT_SET_TRACK("parent.settrack", Type.GROUP),
GROUP_PARENT_ADD("parent.add", Type.GROUP),
GROUP_PARENT_REMOVE("parent.remove", Type.GROUP),
GROUP_PARENT_ADD_TEMP("parent.addtemp", Type.GROUP),
GROUP_PARENT_REMOVE_TEMP("parent.removetemp", Type.GROUP),
GROUP_PARENT_CLEAR("parent.clear", Type.GROUP),
GROUP_PARENT_CLEAR_TRACK("parent.cleartrack", Type.GROUP),
GROUP_META_INFO("meta.info", Type.GROUP),
GROUP_META_SET("meta.set", Type.GROUP),
GROUP_META_UNSET("meta.unset", Type.GROUP),
GROUP_META_SET_TEMP("meta.settemp", Type.GROUP),
GROUP_META_UNSET_TEMP("meta.unsettemp", Type.GROUP),
GROUP_META_ADD_PREFIX("meta.addprefix", Type.GROUP),
GROUP_META_ADD_SUFFIX("meta.addsuffix", Type.GROUP),
GROUP_META_SET_PREFIX("meta.setprefix", Type.GROUP),
GROUP_META_SET_SUFFIX("meta.setsuffix", Type.GROUP),
GROUP_META_REMOVE_PREFIX("meta.removeprefix", Type.GROUP),
GROUP_META_REMOVE_SUFFIX("meta.removesuffix", Type.GROUP),
GROUP_META_ADD_TEMP_PREFIX("meta.addtempprefix", Type.GROUP),
GROUP_META_ADD_TEMP_SUFFIX("meta.addtempsuffix", Type.GROUP),
GROUP_META_SET_TEMP_PREFIX("meta.settempprefix", Type.GROUP),
GROUP_META_SET_TEMP_SUFFIX("meta.settempsuffix", Type.GROUP),
GROUP_META_REMOVE_TEMP_PREFIX("meta.removetempprefix", Type.GROUP),
GROUP_META_REMOVE_TEMP_SUFFIX("meta.removetempsuffix", Type.GROUP),
GROUP_META_CLEAR("meta.clear", Type.GROUP),
GROUP_EDITOR("editor", Type.GROUP),
GROUP_LIST_MEMBERS("listmembers", Type.GROUP),
GROUP_SHOW_TRACKS("showtracks", Type.GROUP),
GROUP_SET_WEIGHT("setweight", Type.GROUP),
GROUP_SET_DISPLAY_NAME("setdisplayname", Type.GROUP),
GROUP_CLEAR("clear", Type.GROUP),
GROUP_RENAME("rename", Type.GROUP),
GROUP_CLONE("clone", Type.GROUP),
TRACK_INFO("info", Type.TRACK, true),
TRACK_EDITOR("editor", Type.TRACK, true),
TRACK_APPEND("append", Type.TRACK, false),
TRACK_INSERT("insert", Type.TRACK, false),
TRACK_REMOVE("remove", Type.TRACK, false),
TRACK_CLEAR("clear", Type.TRACK, false),
TRACK_RENAME("rename", Type.TRACK, false),
TRACK_CLONE("clone", Type.TRACK, false),
TRACK_INFO("info", Type.TRACK),
TRACK_EDITOR("editor", Type.TRACK),
TRACK_APPEND("append", Type.TRACK),
TRACK_INSERT("insert", Type.TRACK),
TRACK_REMOVE("remove", Type.TRACK),
TRACK_CLEAR("clear", Type.TRACK),
TRACK_RENAME("rename", Type.TRACK),
TRACK_CLONE("clone", Type.TRACK),
LOG_RECENT("recent", Type.LOG, true),
LOG_USER_HISTORY("userhistory", Type.LOG, true),
LOG_GROUP_HISTORY("grouphistory", Type.LOG, true),
LOG_TRACK_HISTORY("trackhistory", Type.LOG, true),
LOG_SEARCH("search", Type.LOG, true),
LOG_NOTIFY("notify", Type.LOG, true),
LOG_RECENT("recent", Type.LOG),
LOG_USER_HISTORY("userhistory", Type.LOG),
LOG_GROUP_HISTORY("grouphistory", Type.LOG),
LOG_TRACK_HISTORY("trackhistory", Type.LOG),
LOG_SEARCH("search", Type.LOG),
LOG_NOTIFY("notify", Type.LOG),
SPONGE_PERMISSION_INFO("permission.info", Type.SPONGE, true),
SPONGE_PERMISSION_SET("permission.set", Type.SPONGE, false),
SPONGE_PERMISSION_CLEAR("permission.clear", Type.SPONGE, false),
SPONGE_PARENT_INFO("parent.info", Type.SPONGE, true),
SPONGE_PARENT_ADD("parent.add", Type.SPONGE, false),
SPONGE_PARENT_REMOVE("parent.remove", Type.SPONGE, false),
SPONGE_PARENT_CLEAR("parent.clear", Type.SPONGE, false),
SPONGE_OPTION_INFO("option.info", Type.SPONGE, true),
SPONGE_OPTION_SET("option.set", Type.SPONGE, false),
SPONGE_OPTION_UNSET("option.unset", Type.SPONGE, false),
SPONGE_OPTION_CLEAR("option.clear", Type.SPONGE, false);
SPONGE_PERMISSION_INFO("permission.info", Type.SPONGE),
SPONGE_PERMISSION_SET("permission.set", Type.SPONGE),
SPONGE_PERMISSION_CLEAR("permission.clear", Type.SPONGE),
SPONGE_PARENT_INFO("parent.info", Type.SPONGE),
SPONGE_PARENT_ADD("parent.add", Type.SPONGE),
SPONGE_PARENT_REMOVE("parent.remove", Type.SPONGE),
SPONGE_PARENT_CLEAR("parent.clear", Type.SPONGE),
SPONGE_OPTION_INFO("option.info", Type.SPONGE),
SPONGE_OPTION_SET("option.set", Type.SPONGE),
SPONGE_OPTION_UNSET("option.unset", Type.SPONGE),
SPONGE_OPTION_CLEAR("option.clear", Type.SPONGE);
public static final String ROOT = "luckperms.";
@@ -176,11 +176,9 @@ public enum CommandPermission {
private final String permission;
private final Type type;
private final boolean readOnly;
CommandPermission(String node, Type type, boolean readOnly) {
CommandPermission(String node, Type type) {
this.type = type;
this.readOnly = readOnly;
if (type == Type.NONE) {
this.node = node;
@@ -207,10 +205,6 @@ public enum CommandPermission {
return this.type;
}
public boolean isReadOnly() {
return this.readOnly;
}
public enum Type {
NONE(null),
@@ -28,14 +28,14 @@ package me.lucko.luckperms.common.config;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import me.lucko.luckperms.common.cacheddata.metastack.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.cacheddata.type.SimpleMetaValueSelector;
import me.lucko.luckperms.common.config.generic.KeyedConfiguration;
import me.lucko.luckperms.common.config.generic.key.ConfigKey;
import me.lucko.luckperms.common.config.generic.key.SimpleConfigKey;
import me.lucko.luckperms.common.context.calculator.WorldNameRewriter;
import me.lucko.luckperms.common.graph.TraversalAlgorithm;
import me.lucko.luckperms.common.metastacking.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.metastacking.StandardStackElements;
import me.lucko.luckperms.common.model.PrimaryGroupHolder;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.query.QueryOptionsBuilderImpl;
@@ -246,26 +246,6 @@ public final class ConfigKeys {
*/
public static final ConfigKey<Boolean> REQUIRE_SENDER_GROUP_MEMBERSHIP_TO_MODIFY = booleanKey("require-sender-group-membership-to-modify", false);
/**
* If the plugin is in read-only mode for players
*/
public static final ConfigKey<Boolean> READ_ONLY_MODE_PLAYERS = booleanKey("commands-read-only-mode.players", false);
/**
* If the plugin is in read-only mode for the server console
*/
public static final ConfigKey<Boolean> READ_ONLY_MODE_CONSOLE = booleanKey("commands-read-only-mode.console", false);
/**
* If LuckPerms commands are disabled for players
*/
public static final ConfigKey<Boolean> DISABLE_LUCKPERMS_COMMANDS_PLAYERS = booleanKey("disable-luckperms-commands.players", false);
/**
* If LuckPerms commands are disabled for the server console
*/
public static final ConfigKey<Boolean> DISABLE_LUCKPERMS_COMMANDS_CONSOLE = booleanKey("disable-luckperms-commands.console", false);
/**
* If wildcards are being applied
*/
@@ -390,16 +370,16 @@ public final class ConfigKeys {
/**
* Creates a new prefix MetaStack element based upon the configured values.
*/
public static final ConfigKey<MetaStackDefinition> PREFIX_FORMATTING_OPTIONS = key(c -> {
List<String> format = c.getStringList("meta-formatting.prefix.format", new ArrayList<>());
public static final ConfigKey<MetaStackDefinition> PREFIX_FORMATTING_OPTIONS = key(l -> {
List<String> format = l.getStringList("meta-formatting.prefix.format", new ArrayList<>());
if (format.isEmpty()) {
format.add("highest");
}
String startSpacer = c.getString("meta-formatting.prefix.start-spacer", "");
String middleSpacer = c.getString("meta-formatting.prefix.middle-spacer", " ");
String endSpacer = c.getString("meta-formatting.prefix.end-spacer", "");
String startSpacer = l.getString("meta-formatting.prefix.start-spacer", "");
String middleSpacer = l.getString("meta-formatting.prefix.middle-spacer", " ");
String endSpacer = l.getString("meta-formatting.prefix.end-spacer", "");
DuplicateRemovalFunction duplicateRemovalFunction;
switch (c.getString("meta-formatting.prefix.duplicates", "").toLowerCase(Locale.ROOT)) {
switch (l.getString("meta-formatting.prefix.duplicates", "").toLowerCase(Locale.ROOT)) {
case "first-only":
duplicateRemovalFunction = DuplicateRemovalFunction.FIRST_ONLY;
break;
@@ -411,22 +391,22 @@ public final class ConfigKeys {
break;
}
return new SimpleMetaStackDefinition(StandardStackElements.parseList(c.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
return new SimpleMetaStackDefinition(StandardStackElements.parseList(l.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
});
/**
* Creates a new suffix MetaStack element based upon the configured values.
*/
public static final ConfigKey<MetaStackDefinition> SUFFIX_FORMATTING_OPTIONS = key(c -> {
List<String> format = c.getStringList("meta-formatting.suffix.format", new ArrayList<>());
public static final ConfigKey<MetaStackDefinition> SUFFIX_FORMATTING_OPTIONS = key(l -> {
List<String> format = l.getStringList("meta-formatting.suffix.format", new ArrayList<>());
if (format.isEmpty()) {
format.add("highest");
}
String startSpacer = c.getString("meta-formatting.suffix.start-spacer", "");
String middleSpacer = c.getString("meta-formatting.suffix.middle-spacer", " ");
String endSpacer = c.getString("meta-formatting.suffix.end-spacer", "");
String startSpacer = l.getString("meta-formatting.suffix.start-spacer", "");
String middleSpacer = l.getString("meta-formatting.suffix.middle-spacer", " ");
String endSpacer = l.getString("meta-formatting.suffix.end-spacer", "");
DuplicateRemovalFunction duplicateRemovalFunction;
switch (c.getString("meta-formatting.suffix.duplicates", "").toLowerCase(Locale.ROOT)) {
switch (l.getString("meta-formatting.suffix.duplicates", "").toLowerCase(Locale.ROOT)) {
case "first-only":
duplicateRemovalFunction = DuplicateRemovalFunction.FIRST_ONLY;
break;
@@ -438,7 +418,7 @@ public final class ConfigKeys {
break;
}
return new SimpleMetaStackDefinition(StandardStackElements.parseList(c.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
return new SimpleMetaStackDefinition(StandardStackElements.parseList(l.getPlugin(), format), duplicateRemovalFunction, startSpacer, middleSpacer, endSpacer);
});
/**
@@ -605,16 +585,6 @@ public final class ConfigKeys {
return c.getString("data.mongodb-connection-uri", c.getString("data.mongodb_connection_URI", ""));
}));
/**
* The REST storage URL
*/
public static final ConfigKey<String> REST_STORAGE_URL = notReloadable(stringKey("data.rest-url", "http://localhost:8080/"));
/**
* The REST storage auth key
*/
public static final ConfigKey<String> REST_STORAGE_AUTH_KEY = notReloadable(stringKey("data.rest-auth-key", ""));
/**
* The name of the storage method being used
*/
@@ -1,90 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.config.generic.adapter;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Locale;
public class FileSecretConfigAdapter extends StringBasedConfigurationAdapter {
private static final String PREFIX = "luckperms_";
private final LuckPermsPlugin plugin;
private final Path directory;
public FileSecretConfigAdapter(LuckPermsPlugin plugin, String directory) {
this.plugin = plugin;
this.directory = directory == null ? null : Paths.get(directory);
}
public FileSecretConfigAdapter(LuckPermsPlugin plugin) {
this(plugin, System.getenv("LUCKPERMS_FILE_SECRET_DIRECTORY"));
}
@Override
protected @Nullable String resolveValue(String path) {
if (this.directory == null) {
return null; // not configured
}
// e.g.
// 'server' -> luckperms_server
// 'data.table_prefix' -> luckperms_data_table_prefix
String key = PREFIX + path.toLowerCase(Locale.ROOT)
.replace('-', '_')
.replace('.', '_');
Path resolvedFile = this.directory.resolve(key);
String value;
try {
value = new String(Files.readAllBytes(resolvedFile), StandardCharsets.UTF_8);
} catch (IOException e) {
return null;
}
String printableValue = ConfigKeys.shouldCensorValue(path) ? "*****" : value;
this.plugin.getLogger().info(String.format("Resolved configuration value from file secret: %s = %s", key, printableValue));
return value;
}
@Override
public LuckPermsPlugin getPlugin() {
return this.plugin;
}
@Override
public void reload() {
// no-op
}
}
@@ -46,10 +46,9 @@ import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
/**
* Base implementation of {@link ContextManager}.
* Base implementation of {@link ContextManager} which caches content lookups.
*
* @param <S> the subject type
* @param <P> the player type
* @param <S> the calculator type
*/
public abstract class ContextManager<S, P extends S> {
@@ -78,10 +77,14 @@ public abstract class ContextManager<S, P extends S> {
public abstract UUID getUniqueId(P player);
public abstract QueryOptions getQueryOptions(S subject);
public abstract QueryOptionsSupplier getCacheFor(S subject);
public QueryOptions getQueryOptions(S subject) {
return getCacheFor(subject).getQueryOptions();
}
public ImmutableContextSet getContext(S subject) {
return getQueryOptions(subject).context();
return getCacheFor(subject).getContextSet();
}
public QueryOptions getStaticQueryOptions() {
@@ -92,13 +95,11 @@ public abstract class ContextManager<S, P extends S> {
return getStaticQueryOptions().context();
}
public void customizeStaticQueryOptions(QueryOptions.Builder builder) {
// overridden
public QueryOptions formQueryOptions(ImmutableContextSet contextSet) {
return this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(contextSet).build();
}
public void customizeQueryOptions(S subject, QueryOptions.Builder builder) {
// overridden
}
public abstract QueryOptions formQueryOptions(S subject, ImmutableContextSet contextSet);
public void signalContextUpdate(S subject) {
if (subject == null) {
@@ -112,7 +113,7 @@ public abstract class ContextManager<S, P extends S> {
this.plugin.getEventDispatcher().dispatchContextUpdate(subject);
}
public abstract void invalidateCache(S subject);
protected abstract void invalidateCache(S subject);
public void registerCalculator(ContextCalculator<? super S> calculator) {
String calculatorClass = calculator.getClass().getName();
@@ -156,9 +157,7 @@ public abstract class ContextManager<S, P extends S> {
callContextCalculator(calculator, subject, consumer);
}
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(accumulator.build());
customizeQueryOptions(subject, builder);
return builder.build();
return formQueryOptions(subject, accumulator.build());
}
private QueryOptions calculateStatic() {
@@ -169,9 +168,7 @@ public abstract class ContextManager<S, P extends S> {
callStaticContextCalculator(calculator, consumer);
}
QueryOptions.Builder builder = this.plugin.getConfiguration().get(ConfigKeys.GLOBAL_QUERY_OPTIONS).toBuilder().context(accumulator.build());
customizeStaticQueryOptions(builder);
return builder.build();
return formQueryOptions(accumulator.build());
}
public ImmutableContextSet getPotentialContexts() {
@@ -1,73 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.context.manager;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.query.QueryOptions;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.concurrent.TimeUnit;
/**
* Implementation of {@link ContextManager} which utilises 'detached' supplier caches stored alongside the subject instances.
*/
public abstract class DetachedContextManager<S, P extends S> extends ContextManager<S, P> {
private final LoadingCache<S, QueryOptions> fallbackContextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
protected DetachedContextManager(LuckPermsPlugin plugin, Class<S> subjectClass, Class<P> playerClass) {
super(plugin, subjectClass, playerClass);
}
@Override
public QueryOptions getQueryOptions(S subject) {
QueryOptionsSupplier supplier = getQueryOptionsSupplier(subject);
if (supplier != null) {
return supplier.getQueryOptions();
}
return this.fallbackContextsCache.get(subject);
}
@Override
public void invalidateCache(S subject) {
QueryOptionsSupplier queryOptionsSupplier = getQueryOptionsSupplier(subject);
if (queryOptionsSupplier != null) {
queryOptionsSupplier.invalidateCache();
}
this.fallbackContextsCache.invalidate(subject);
}
public QueryOptionsSupplier createQueryOptionsSupplier(S subject) {
return new QueryOptionsCache<>(subject, this);
}
public abstract @Nullable QueryOptionsSupplier getQueryOptionsSupplier(S subject);
}
@@ -23,23 +23,22 @@
* SOFTWARE.
*/
package me.lucko.luckperms.neoforge;
package me.lucko.luckperms.common.context.manager;
import me.lucko.luckperms.common.plugin.scheduler.AbstractJavaScheduler;
import com.github.benmanes.caffeine.cache.LoadingCache;
import net.luckperms.api.query.QueryOptions;
import java.util.concurrent.Executor;
public final class InlineQueryOptionsSupplier<T> implements QueryOptionsSupplier {
private final T key;
private final LoadingCache<T, QueryOptions> cache;
public class NeoForgeSchedulerAdapter extends AbstractJavaScheduler {
private final Executor sync;
public NeoForgeSchedulerAdapter(LPNeoForgeBootstrap bootstrap) {
super(bootstrap);
this.sync = r -> bootstrap.getServer().orElseThrow(() -> new IllegalStateException("Server not ready")).executeBlocking(r);
public InlineQueryOptionsSupplier(T key, LoadingCache<T, QueryOptions> cache) {
this.key = key;
this.cache = cache;
}
@Override
public Executor sync() {
return this.sync;
public QueryOptions getQueryOptions() {
return this.cache.get(this.key);
}
}
@@ -37,11 +37,11 @@ import java.util.concurrent.TimeUnit;
*
* @param <T> the player type
*/
final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> implements QueryOptionsSupplier {
public final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> implements QueryOptionsSupplier {
private final T subject;
private final ContextManager<T, ?> contextManager;
QueryOptionsCache(T subject, ContextManager<T, ?> contextManager) {
public QueryOptionsCache(T subject, ContextManager<T, ?> contextManager) {
super(50L, TimeUnit.MILLISECONDS); // expire roughly every tick
this.subject = subject;
this.contextManager = contextManager;
@@ -61,9 +61,4 @@ final class QueryOptionsCache<T> extends ExpiringCache<QueryOptions> implements
public ImmutableContextSet getContextSet() {
return get().context();
}
@Override
public void invalidateCache() {
invalidate();
}
}
@@ -39,6 +39,4 @@ public interface QueryOptionsSupplier {
return getQueryOptions().context();
}
void invalidateCache();
}
@@ -1,54 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.context.manager;
import com.github.benmanes.caffeine.cache.LoadingCache;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.CaffeineFactory;
import net.luckperms.api.query.QueryOptions;
import java.util.concurrent.TimeUnit;
public abstract class SimpleContextManager<S, P extends S> extends ContextManager<S, P> {
private final LoadingCache<S, QueryOptions> contextsCache = CaffeineFactory.newBuilder()
.expireAfterWrite(50, TimeUnit.MILLISECONDS)
.build(this::calculate);
protected SimpleContextManager(LuckPermsPlugin plugin, Class<S> subjectClass, Class<P> playerClass) {
super(plugin, subjectClass, playerClass);
}
@Override
public QueryOptions getQueryOptions(S subject) {
return this.contextsCache.get(subject);
}
@Override
public void invalidateCache(S subject) {
this.contextsCache.invalidate(subject);
}
}
@@ -62,28 +62,28 @@ public enum Dependency {
ADVENTURE(
"me{}lucko",
"adventure-api",
"4.21.1",
"kQJlZ0gUxdTRRkskT43qiy2kpt9s654LvB0nqoCP6YE=",
"4.13.0",
"gcmYlY1KDrzbnE9nB8rA44oBZKPSMvv9kNRefoEuud0=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
ADVENTURE_PLATFORM(
"me{}lucko",
"adventure-platform-api",
"4.21.1",
"4.13.0",
"Kk8IkEMVa9ITBfC3yocpcXQiZ9CwN9VxeWjKUD8I0n0=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
ADVENTURE_PLATFORM_BUKKIT(
"me{}lucko",
"adventure-platform-bukkit",
"4.21.1",
"NffwBnfT/Mc6VpsmTcaXPvckv9T4vPJD83adt0C8vao=",
"4.13.3",
"ZXw7pf+a6tjGdB2WO1rB/KQ+ETLAH8kQFSu3El7jopY=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
ADVENTURE_PLATFORM_BUNGEECORD(
"me{}lucko",
"adventure-platform-bungeecord",
"4.21.1",
"4.13.3",
"7hnndD6dO6LZoRbtOBdn6OFK0y/T2PqNLHxCg6zaQlo=",
Relocation.of("adventure", "net{}kyori{}adventure")
),
@@ -138,7 +138,7 @@ public enum DependencyRepository {
}
/**
* Downloads the {@code dependency} to the {@code file}, ensuring the
* Downloads the the {@code dependency} to the {@code file}, ensuring the
* downloaded bytes match the checksum.
*
* @param dependency the dependency to download
@@ -179,13 +179,6 @@ public interface Message {
.color(GRAY)
);
Args0 COMMANDS_DISABLED = () -> prefixed(translatable()
// "&3LuckPerms commands are disabled."
.key("luckperms.commandsystem.commands-disabled")
.color(DARK_AQUA)
.append(FULL_STOP)
);
Args2<String, String> FIRST_TIME_SETUP = (label, username) -> joinNewline(
// "&3It seems that no permissions have been setup yet!"
// "&3Before you can use any of the LuckPerms commands in-game, you need to use the console to give yourself access."
@@ -54,14 +54,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
public class LuckPermsMessagingService extends AsyncInterface implements InternalMessagingService, IncomingMessageConsumer {
private final LuckPermsPlugin plugin;
private final Set<UUID> receivedMessages;
private final ExpiringSet<UUID> receivedMessages;
private final PushUpdateBuffer updateBuffer;
private final MessengerProvider messengerProvider;
@@ -75,7 +74,7 @@ public class LuckPermsMessagingService extends AsyncInterface implements Interna
this.messenger = messengerProvider.obtain(this);
Objects.requireNonNull(this.messenger, "messenger");
this.receivedMessages = ExpiringSet.newExpiringSet(5, TimeUnit.MINUTES);
this.receivedMessages = new ExpiringSet<>(5, TimeUnit.MINUTES);
this.updateBuffer = new PushUpdateBuffer(plugin);
}
@@ -86,6 +86,6 @@ public class SqlMessenger extends AbstractSqlMessenger {
@Override
protected String getTableName() {
return this.sqlStorage.getStatementProcessor().process("{prefix}messenger");
return this.sqlStorage.getStatementProcessor().apply("{prefix}messenger");
}
}
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata.metastack;
package me.lucko.luckperms.common.metastacking;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata.metastack;
package me.lucko.luckperms.common.metastacking;
import com.google.common.collect.ImmutableList;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
@@ -23,7 +23,7 @@
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata.metastack;
package me.lucko.luckperms.common.metastacking;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
@@ -220,11 +220,8 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
Track track = this.plugin.getTrackManager().getIfLoaded(this.trackName);
if (track == null) {
return false;
}
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName());
return track != null && origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName());
}
@Override
@@ -253,11 +250,8 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
Track track = this.plugin.getTrackManager().getIfLoaded(this.trackName);
if (track == null) {
return false;
}
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return !(origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && track.containsGroup(origin.getName()));
return track != null && !track.containsGroup(origin.getName());
}
@Override
@@ -311,7 +305,7 @@ public final class StandardStackElements {
@Override
public boolean shouldAccumulate(@NonNull ChatMetaType type, @NonNull ChatMetaNode<?, ?> node, @Nullable ChatMetaNode<?, ?> current) {
PermissionHolder.Identifier origin = node.metadata(InheritanceOriginMetadata.KEY).getOrigin();
return !(origin.getType().equals(PermissionHolder.Identifier.GROUP_TYPE) && this.groupName.equals(origin.getName()));
return !this.groupName.equals(origin.getName());
}
@Override
@@ -30,7 +30,6 @@ import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.util.ExpiringSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@@ -42,16 +41,16 @@ public class UserHousekeeper implements Runnable {
private final UserManager<?> userManager;
// contains the uuids of users who have recently logged in / out
private final Set<UUID> recentlyUsed;
private final ExpiringSet<UUID> recentlyUsed;
// contains the uuids of users who have recently been retrieved from the API
private final Set<UUID> recentlyUsedApi;
private final ExpiringSet<UUID> recentlyUsedApi;
public UserHousekeeper(LuckPermsPlugin plugin, UserManager<?> userManager, TimeoutSettings timeoutSettings) {
this.plugin = plugin;
this.userManager = userManager;
this.recentlyUsed = ExpiringSet.newExpiringSet(timeoutSettings.duration, timeoutSettings.unit);
this.recentlyUsedApi = ExpiringSet.newExpiringSet(5, TimeUnit.MINUTES);
this.recentlyUsed = new ExpiringSet<>(timeoutSettings.duration, timeoutSettings.unit);
this.recentlyUsedApi = new ExpiringSet<>(5, TimeUnit.MINUTES);
}
// called when a player attempts a connection or logs out
@@ -72,7 +72,7 @@ public final class StandardNodeMatchers {
return new TypeEquals<>(type);
}
public static class Generic extends ConstraintNodeMatcher<Node> {
private static class Generic extends ConstraintNodeMatcher<Node> {
Generic(Comparison comparison, String value) {
super(comparison, value);
}
@@ -83,7 +83,7 @@ public final class StandardNodeMatchers {
}
}
public static final class NodeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
private static final class NodeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
private final T node;
private final NodeEqualityPredicate equalityPredicate;
@@ -93,14 +93,6 @@ public final class StandardNodeMatchers {
this.equalityPredicate = equalityPredicate;
}
public T getNode() {
return this.node;
}
public NodeEqualityPredicate getEqualityPredicate() {
return this.equalityPredicate;
}
@SuppressWarnings("unchecked")
@Override
public @Nullable T filterConstraintMatch(@NonNull Node node) {
@@ -111,16 +103,9 @@ public final class StandardNodeMatchers {
}
}
public static final class MetaKeyEquals extends ConstraintNodeMatcher<MetaNode> {
private final String metaKey;
private static final class MetaKeyEquals extends ConstraintNodeMatcher<MetaNode> {
MetaKeyEquals(String metaKey) {
super(Comparison.SIMILAR, Meta.key(metaKey, Comparison.WILDCARD));
this.metaKey = metaKey;
}
public String getMetaKey() {
return this.metaKey;
}
@Override
@@ -129,7 +114,7 @@ public final class StandardNodeMatchers {
}
}
public static final class TypeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
private static final class TypeEquals<T extends Node> extends ConstraintNodeMatcher<T> {
private final NodeType<? extends T> type;
TypeEquals(NodeType<? extends T> type) {
@@ -137,10 +122,6 @@ public final class StandardNodeMatchers {
this.type = type;
}
public NodeType<? extends T> getType() {
return this.type;
}
@Override
public @Nullable T filterConstraintMatch(@NonNull Node node) {
return this.type.tryCast(node).orElse(null);
@@ -210,17 +210,12 @@ public enum ShorthandParser {
* Implements an iterator over a given range of ints.
*/
private abstract static class RangeIterator implements Iterator<String> {
private static final int MAX_RANGE = 250;
private final int max;
private int next;
RangeIterator(int a, int b) {
this.max = Math.max(a, b);
this.next = Math.min(a, b);
if ((this.max - this.next) > MAX_RANGE) {
throw new IllegalArgumentException("Range too large");
}
}
protected abstract String toString(int i);
@@ -33,7 +33,6 @@ import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.LuckPermsConfiguration;
import me.lucko.luckperms.common.config.generic.adapter.ConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.EnvironmentVariableConfigAdapter;
import me.lucko.luckperms.common.config.generic.adapter.FileSecretConfigAdapter;
import me.lucko.luckperms.common.config.generic.adapter.MultiConfigurationAdapter;
import me.lucko.luckperms.common.config.generic.adapter.SystemPropertyConfigAdapter;
import me.lucko.luckperms.common.context.calculator.ConfigurationContextCalculator;
@@ -62,7 +61,6 @@ import me.lucko.luckperms.common.storage.misc.DataConstraints;
import me.lucko.luckperms.common.tasks.CacheHousekeepingTask;
import me.lucko.luckperms.common.tasks.ExpireTemporaryTask;
import me.lucko.luckperms.common.tasks.SyncTask;
import me.lucko.luckperms.common.treeview.AsyncPermissionRegistry;
import me.lucko.luckperms.common.treeview.PermissionRegistry;
import me.lucko.luckperms.common.verbose.VerboseHandler;
import me.lucko.luckperms.common.webeditor.socket.WebEditorSocket;
@@ -94,10 +92,10 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
// init during load
private DependencyManager dependencyManager;
private TranslationManager translationManager;
private AsyncPermissionRegistry permissionRegistry;
private VerboseHandler verboseHandler;
// init during enable
private VerboseHandler verboseHandler;
private PermissionRegistry permissionRegistry;
private LogDispatcher logDispatcher;
private LuckPermsConfiguration configuration;
private OkHttpClient httpClient;
@@ -130,8 +128,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
this.translationManager.reload();
// load some utilities early
this.permissionRegistry = new AsyncPermissionRegistry(getBootstrap().getScheduler());
this.verboseHandler = new VerboseHandler(getBootstrap().getScheduler());
this.permissionRegistry = new PermissionRegistry(getBootstrap().getScheduler());
}
public final void enable() {
@@ -141,14 +138,14 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
// send the startup banner
Message.STARTUP_BANNER.send(getConsoleSender(), getBootstrap());
// setup log dispatcher instance early
// load some utilities early
this.verboseHandler = new VerboseHandler(getBootstrap().getScheduler());
this.logDispatcher = new LogDispatcher(this);
// load configuration
getLogger().info("Loading configuration...");
ConfigurationAdapter configFileAdapter = provideConfigurationAdapter();
this.configuration = new LuckPermsConfiguration(this, new MultiConfigurationAdapter(this,
new FileSecretConfigAdapter(this),
new SystemPropertyConfigAdapter(this),
new EnvironmentVariableConfigAdapter(this),
configFileAdapter
@@ -208,11 +205,7 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
this.syncTaskBuffer = new SyncTask.Buffer(this);
// register commands
if (skipCommandRegistration()) {
getLogger().warn("LuckPerms commands are disabled in the configuration for both console and players. Skipping command registration.");
} else {
registerCommands();
}
registerCommands();
// load internal managers
getLogger().info("Loading internal permission managers...");
@@ -392,11 +385,6 @@ public abstract class AbstractLuckPermsPlugin implements LuckPermsPlugin {
return configFile;
}
protected boolean skipCommandRegistration() {
return getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_CONSOLE) &&
getConfiguration().get(ConfigKeys.DISABLE_LUCKPERMS_COMMANDS_PLAYERS);
}
@Override
public PluginLogger getLogger() {
return getBootstrap().getPluginLogger();
@@ -54,7 +54,7 @@ final class FlagUtils {
private static byte toByte0(Set<Flag> settings) {
byte b = 0;
for (Flag setting : settings) {
b |= (byte) (1 << setting.ordinal());
b |= 1 << setting.ordinal();
}
return b;
}
@@ -56,7 +56,7 @@ public class QueryOptionsImpl implements QueryOptions {
// computed based on state above
private final int hashCode;
private Set<Flag> flagsSet = null;
private final ContextSatisfyMode overrideContextSatisfyMode;
private final ContextSatisfyMode contextSatisfyMode;
QueryOptionsImpl(QueryMode mode, @Nullable ImmutableContextSet context, byte flags, @Nullable Map<OptionKey<?>, Object> options) {
this.mode = mode;
@@ -65,7 +65,7 @@ public class QueryOptionsImpl implements QueryOptions {
this.options = options == null ? null : ImmutableMap.copyOf(options);
this.hashCode = calculateHashCode();
this.overrideContextSatisfyMode = options == null ? null : (ContextSatisfyMode) options.get(ContextSatisfyMode.KEY);
this.contextSatisfyMode = options == null ? null : (ContextSatisfyMode) options.get(ContextSatisfyMode.KEY);
}
@Override
@@ -119,7 +119,7 @@ public class QueryOptionsImpl implements QueryOptions {
public boolean satisfies(@NonNull ContextSet contextSet, @NonNull ContextSatisfyMode defaultContextSatisfyMode) {
switch (this.mode) {
case CONTEXTUAL:
return contextSet.isSatisfiedBy(this.context, this.overrideContextSatisfyMode == null ? defaultContextSatisfyMode : this.overrideContextSatisfyMode);
return contextSet.isSatisfiedBy(this.context, this.contextSatisfyMode == null ? defaultContextSatisfyMode : this.contextSatisfyMode);
case NON_CONTEXTUAL:
return true;
default:
@@ -26,12 +26,8 @@
package me.lucko.luckperms.common.sender;
import com.google.common.collect.Iterables;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.config.generic.key.ConfigKey;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.TextComponent;
import net.luckperms.api.util.Tristate;
@@ -101,19 +97,6 @@ public final class AbstractSender<T> implements Sender {
return isConsole() || this.factory.hasPermission(this.sender, permission);
}
@Override
public boolean hasPermission(CommandPermission permission) {
boolean readOnlyMode = isConsole()
? this.plugin.getConfiguration().get(ConfigKeys.READ_ONLY_MODE_CONSOLE)
: this.plugin.getConfiguration().get(ConfigKeys.READ_ONLY_MODE_PLAYERS);
if (readOnlyMode && !permission.isReadOnly()) {
return false;
}
return hasPermission(permission.getPermission());
}
@Override
public void performCommand(String commandLine) {
this.factory.performCommand(this.sender, commandLine);
@@ -172,7 +155,7 @@ public final class AbstractSender<T> implements Sender {
case 1:
return input.get(0);
default:
return Component.join(JoinConfiguration.separator(Component.empty()), input);
return Component.join(Component.empty(), input);
}
});
}
@@ -25,8 +25,6 @@
package me.lucko.luckperms.common.sender;
import me.lucko.luckperms.common.command.access.CommandPermission;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.util.Tristate;
@@ -49,16 +47,6 @@ public abstract class DummyConsoleSender implements Sender {
return true;
}
@Override
public boolean hasPermission(CommandPermission permission) {
boolean readOnlyMode = this.platform.getConfiguration().get(ConfigKeys.READ_ONLY_MODE_CONSOLE);
if (readOnlyMode && !permission.isReadOnly()) {
return false;
}
return true;
}
@Override
public void performCommand(String commandLine) {
@@ -132,7 +132,9 @@ public interface Sender {
* @param permission the permission to check for
* @return true if the sender has the permission
*/
boolean hasPermission(CommandPermission permission);
default boolean hasPermission(CommandPermission permission) {
return hasPermission(permission.getPermission());
}
/**
* Makes the sender perform a command.
@@ -37,7 +37,6 @@ import me.lucko.luckperms.common.storage.implementation.file.loader.JsonLoader;
import me.lucko.luckperms.common.storage.implementation.file.loader.TomlLoader;
import me.lucko.luckperms.common.storage.implementation.file.loader.YamlLoader;
import me.lucko.luckperms.common.storage.implementation.mongodb.MongoStorage;
import me.lucko.luckperms.common.storage.implementation.rest.RestStorage;
import me.lucko.luckperms.common.storage.implementation.split.SplitStorage;
import me.lucko.luckperms.common.storage.implementation.split.SplitStorageType;
import me.lucko.luckperms.common.storage.implementation.sql.SqlStorage;
@@ -130,12 +129,6 @@ public class StorageFactory {
this.plugin.getConfiguration().get(ConfigKeys.MONGODB_COLLECTION_PREFIX),
this.plugin.getConfiguration().get(ConfigKeys.MONGODB_CONNECTION_URI)
);
case REST:
return new RestStorage(
this.plugin,
this.plugin.getConfiguration().get(ConfigKeys.REST_STORAGE_URL),
this.plugin.getConfiguration().get(ConfigKeys.REST_STORAGE_AUTH_KEY)
);
case YAML:
return new SeparatedConfigurateStorage(this.plugin, "YAML", new YamlLoader(), ".yml", "yaml-storage");
case JSON:
@@ -51,9 +51,6 @@ public enum StorageType {
SQLITE("SQLite", "sqlite"),
H2("H2", "h2"),
// REST
REST("REST", "rest"),
// Custom
CUSTOM("Custom", "custom");
@@ -36,7 +36,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
@@ -98,7 +97,7 @@ public class FileWatcher extends AbstractFileWatcher {
private final Path path;
/** A set of files which have been modified recently */
private final Set<String> recentlyModifiedFiles = ExpiringSet.newExpiringSet(4, TimeUnit.SECONDS);
private final ExpiringSet<String> recentlyModifiedFiles = new ExpiringSet<>(4, TimeUnit.SECONDS);
/** The listener callback functions */
private final List<Consumer<Path>> callbacks = new CopyOnWriteArrayList<>();
@@ -25,7 +25,6 @@
package me.lucko.luckperms.common.storage.implementation.mongodb;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
@@ -587,8 +586,7 @@ public class MongoStorage implements StorageImplementation {
return new Document("_id", track.getName()).append("groups", track.getGroups());
}
@VisibleForTesting
static Document nodeToDoc(Node node) {
private static Document nodeToDoc(Node node) {
Document document = new Document()
.append("key", node.getKey())
.append("value", node.getValue());
@@ -605,8 +603,7 @@ public class MongoStorage implements StorageImplementation {
return document;
}
@VisibleForTesting
static Node nodeFromDoc(Document document) {
private static Node nodeFromDoc(Document document) {
String key = document.containsKey("permission") ? document.getString("permission") : document.getString("key");
if (key == null || key.isEmpty()) {
@@ -625,7 +622,7 @@ public class MongoStorage implements StorageImplementation {
}
if (document.containsKey("expiry")) {
builder.expiry(((Number) document.get("expiry")).longValue());
builder.expiry((long) document.get("expiry"));
}
if (document.containsKey("context") && document.get("context") instanceof List) {
@@ -1,664 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.implementation.rest;
import com.google.common.collect.ImmutableList;
import me.lucko.luckperms.common.actionlog.LogPage;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.actionlog.filter.ActionFields;
import me.lucko.luckperms.common.bulkupdate.BulkUpdate;
import me.lucko.luckperms.common.context.ImmutableContextSetImpl;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.filter.Constraint;
import me.lucko.luckperms.common.filter.Filter;
import me.lucko.luckperms.common.filter.FilterField;
import me.lucko.luckperms.common.filter.FilterList;
import me.lucko.luckperms.common.filter.PageParameters;
import me.lucko.luckperms.common.model.Group;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.User;
import me.lucko.luckperms.common.node.factory.NodeBuilders;
import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher;
import me.lucko.luckperms.common.node.matcher.StandardNodeMatchers;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.StorageMetadata;
import me.lucko.luckperms.common.storage.implementation.StorageImplementation;
import me.lucko.luckperms.common.storage.misc.NodeEntry;
import me.lucko.luckperms.common.storage.misc.PlayerSaveResultImpl;
import me.lucko.luckperms.common.util.Difference;
import me.lucko.luckperms.common.util.Iterators;
import net.luckperms.api.actionlog.Action;
import net.luckperms.api.context.ContextSet;
import net.luckperms.api.model.PlayerSaveResult;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeBuilder;
import net.luckperms.api.node.NodeType;
import net.luckperms.rest.LuckPermsRestClient;
import net.luckperms.rest.model.ActionPage;
import net.luckperms.rest.model.Context;
import net.luckperms.rest.model.CreateGroupRequest;
import net.luckperms.rest.model.CreateTrackRequest;
import net.luckperms.rest.model.CreateUserRequest;
import net.luckperms.rest.model.GroupSearchResult;
import net.luckperms.rest.model.Health;
import net.luckperms.rest.model.UpdateTrackRequest;
import net.luckperms.rest.model.UpdateUserRequest;
import net.luckperms.rest.model.UserLookupResult;
import net.luckperms.rest.model.UserSearchResult;
import org.checkerframework.checker.nullness.qual.Nullable;
import retrofit2.Response;
import java.io.IOException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
public class RestStorage implements StorageImplementation {
private final LuckPermsPlugin plugin;
private final LuckPermsRestClient client;
public RestStorage(LuckPermsPlugin plugin, String baseUrl, String apiKey) {
this.plugin = plugin;
this.client = LuckPermsRestClient.builder()
.baseUrl(baseUrl)
.apiKey(apiKey)
.build();
}
@Override
public LuckPermsPlugin getPlugin() {
return this.plugin;
}
@Override
public String getImplementationName() {
return "REST";
}
@Override
public void init() throws IOException {
Health health = this.client.misc().health().execute().body();
if (health == null || !health.healthy()) {
this.plugin.getLogger().warn("REST storage endpoint is unhealthy");
}
}
@Override
public void shutdown() {
this.client.close();
}
@Override
public StorageMetadata getMeta() {
StorageMetadata metadata = new StorageMetadata();
boolean success = true;
long start = System.currentTimeMillis();
try {
Health health = this.client.misc().health().execute().body();
if (health == null || !health.healthy()) {
success = false;
}
} catch (IOException e) {
success = false;
}
if (success) {
int duration = (int) (System.currentTimeMillis() - start);
metadata.ping(duration);
}
metadata.connected(success);
return metadata;
}
@Override
public void logAction(Action entry) throws IOException {
this.client.actions().submit(convertAction(entry)).execute();
}
@Override
public LogPage getLogPage(FilterList<Action> filters, @Nullable PageParameters page) throws Exception {
Response<ActionPage> resp = null;
if (filters.isEmpty()) {
// ActionFilters.all()
resp = page != null
? this.client.actions().query(page.pageSize(), page.pageNumber()).execute()
: this.client.actions().query().execute();
} else if (filters.size() == 1) {
// ActionFilters.source(uniqueId)
Filter<Action, ?> filter = filters.get(0);
if (filter.field() == ActionFields.SOURCE_UNIQUE_ID && filter.constraint().comparison() == Comparison.EQUAL) {
UUID uniqueId = (UUID) filter.constraint().value();
resp = page != null
? this.client.actions().querySource(uniqueId, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().querySource(uniqueId).execute();
}
} else if (filters.operator() == FilterList.LogicalOperator.AND && filters.size() == 2) {
Filter<Action, ?> filterA = filters.get(0);
Filter<Action, ?> filterB = filters.get(1);
if (filterA.field() == ActionFields.TARGET_TYPE && filterA.constraint().comparison() == Comparison.EQUAL) {
Action.Target.Type type = (Action.Target.Type) filterA.constraint().value();
if (type == Action.Target.Type.USER) {
// ActionFilters.user(uniqueId)
if (filterB.field() == ActionFields.TARGET_UNIQUE_ID && filterB.constraint().comparison() == Comparison.EQUAL) {
UUID uniqueId = (UUID) filterB.constraint().value();
resp = page != null
? this.client.actions().queryTargetUser(uniqueId, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().queryTargetUser(uniqueId).execute();
}
} else if (type == Action.Target.Type.GROUP) {
// ActionFilters.group(name)
if (filterB.field() == ActionFields.TARGET_NAME && filterB.constraint().comparison() == Comparison.EQUAL) {
String name = (String) filterB.constraint().value();
resp = page != null
? this.client.actions().queryTargetGroup(name, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().queryTargetGroup(name).execute();
}
} else if (type == Action.Target.Type.TRACK) {
// ActionFilters.track(name)
if (filterB.field() == ActionFields.TARGET_NAME && filterB.constraint().comparison() == Comparison.EQUAL) {
String name = (String) filterB.constraint().value();
resp = page != null
? this.client.actions().queryTargetTrack(name, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().queryTargetTrack(name).execute();
}
}
}
} else if (filters.operator() == FilterList.LogicalOperator.OR && filters.size() == 3) {
// ActionFilters.search(query)
ImmutableList<FilterField<Action, String>> searchFields = ImmutableList.of(ActionFields.SOURCE_NAME, ActionFields.TARGET_NAME, ActionFields.DESCRIPTION);
if (filters.stream().allMatch(filter -> filter.constraint().comparison() == Comparison.SIMILAR && searchFields.contains(filter.field()))) {
String query = (String) filters.get(0).constraint().value();
if (!query.startsWith("%") || !query.endsWith("%")) {
throw new IllegalArgumentException("Unsupported search query: " + query);
}
String searchTerm = query.substring(1, query.length() - 1);
resp = page != null
? this.client.actions().querySearch(searchTerm, page.pageSize(), page.pageNumber()).execute()
: this.client.actions().querySearch(searchTerm).execute();
}
}
if (resp == null) {
throw new UnsupportedOperationException("Unsupported filter: " + filters);
}
ActionPage body = Objects.requireNonNull(resp.body(), "resp.body()");
return LogPage.of(
body.entries().stream().map(RestStorage::convertAction).collect(Collectors.toList()),
page,
body.overallSize()
);
}
@Override
public void applyBulkUpdate(BulkUpdate bulkUpdate) {
throw new UnsupportedOperationException(); // TODO
}
@Override
public User loadUser(UUID uniqueId, String username) throws Exception {
net.luckperms.rest.model.User remoteUser = this.client.users().get(uniqueId).execute().body();
if (remoteUser == null) {
throw new IllegalStateException("Client did not return a user for " + uniqueId);
}
User user = this.plugin.getUserManager().getOrMake(uniqueId, username);
user.setUsername(remoteUser.username(), true);
user.loadNodesFromStorage(remoteUser.nodes().stream().map(RestStorage::convertNode).collect(Collectors.toList()));
return user;
}
@Override
public Map<UUID, User> loadUsers(Set<UUID> uniqueIds) throws Exception {
return uniqueIds.parallelStream()
.map(uniqueId -> {
try {
return loadUser(uniqueId, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toMap(User::getUniqueId, Function.identity()));
}
@Override
public void saveUser(User user) throws Exception {
Difference<Node> changes = user.normalData().exportChanges(results -> {
if (this.plugin.getUserManager().isNonDefaultUser(user)) {
return true;
}
// if the only change is adding the default node, we don't need to export
if (results.getChanges().size() == 1) {
Difference.Change<Node> onlyChange = results.getChanges().iterator().next();
return !(onlyChange.type() == Difference.ChangeType.ADD && this.plugin.getUserManager().isDefaultNode(onlyChange.value()));
}
return true;
});
if (changes == null) {
return;
}
String username = user.getUsername().orElse(null);
if (username != null) {
this.client.users().update(user.getUniqueId(), new UpdateUserRequest(username)).execute();
}
Set<Node> added = changes.getAdded();
Set<Node> removed = changes.getRemoved();
if (!removed.isEmpty()) {
this.client.users().nodesDelete(user.getUniqueId(), removed.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
if (!added.isEmpty()) {
this.client.users().nodesAdd(user.getUniqueId(), added.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
}
@Override
public Set<UUID> getUniqueUsers() throws Exception {
return this.client.users().list().execute().body();
}
@Override
public <N extends Node> List<NodeEntry<UUID, N>> searchUserNodes(ConstraintNodeMatcher<N> matcher) throws Exception {
List<UserSearchResult> results;
if (matcher instanceof StandardNodeMatchers.TypeEquals) {
NodeType<? extends N> type = ((StandardNodeMatchers.TypeEquals<N>) matcher).getType();
results = this.client.users().searchNodesByType(convertNodeType(type)).execute().body();
} else {
Constraint<String> constraint = matcher.getConstraint();
Comparison comparison = constraint.comparison();
String value = constraint.value();
if (comparison == Comparison.EQUAL) {
results = this.client.users().searchNodesByKey(value).execute().body();
} else if (comparison == Comparison.SIMILAR) {
long wildcards = value.chars().filter(ch -> ch == '%').count();
if (wildcards == 0) {
results = this.client.users().searchNodesByKey(value).execute().body();
} else if (wildcards == 1 && value.endsWith("%")) {
results = this.client.users().searchNodesByKeyStartsWith(value.substring(0, value.length() - 1)).execute().body();
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
}
if (results == null) {
throw new IllegalStateException("Client returned null results");
}
List<NodeEntry<UUID, N>> held = new ArrayList<>();
for (UserSearchResult result : results) {
UUID uniqueId = result.uniqueId();
for (net.luckperms.rest.model.Node node : result.results()) {
N match = matcher.filterConstraintMatch(convertNode(node));
if (match != null) {
held.add(NodeEntry.of(uniqueId, match));
}
}
}
return held;
}
@Override
public Group createAndLoadGroup(String name) throws Exception {
net.luckperms.rest.model.Group remoteGroup = this.client.groups().create(new CreateGroupRequest(name)).execute().body();
if (remoteGroup == null) {
remoteGroup = this.client.groups().get(name).execute().body();
if (remoteGroup == null) {
throw new IllegalStateException("Unable to create group: " + name);
}
}
Group group = this.plugin.getGroupManager().getOrMake(name);
group.loadNodesFromStorage(remoteGroup.nodes().stream().map(RestStorage::convertNode).collect(Collectors.toList()));
return group;
}
@Override
public Optional<Group> loadGroup(String name) throws Exception {
net.luckperms.rest.model.Group remoteGroup = this.client.groups().get(name).execute().body();
if (remoteGroup == null) {
return Optional.empty();
}
Group group = this.plugin.getGroupManager().getOrMake(name);
group.loadNodesFromStorage(remoteGroup.nodes().stream().map(RestStorage::convertNode).collect(Collectors.toList()));
return Optional.of(group);
}
@Override
public void loadAllGroups() throws Exception {
Set<String> groups = this.client.groups().list().execute().body();
if (groups == null) {
throw new IllegalStateException("Client returned a null list of groups");
}
if (!Iterators.tryIterate(groups, this::loadGroup)) {
throw new RuntimeException("Exception occurred whilst loading a group");
}
this.plugin.getGroupManager().retainAll(groups);
}
@Override
public void saveGroup(Group group) throws Exception {
Difference<Node> changes = group.normalData().exportChanges(c -> true);
Set<Node> added = changes.getAdded();
Set<Node> removed = changes.getRemoved();
if (!removed.isEmpty()) {
this.client.groups().nodesDelete(group.getName(), removed.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
if (!added.isEmpty()) {
this.client.groups().nodesAdd(group.getName(), added.stream().map(RestStorage::convertNode).collect(Collectors.toList())).execute();
}
}
@Override
public void deleteGroup(Group group) throws Exception {
this.client.groups().delete(group.getName()).execute();
}
@Override
public <N extends Node> List<NodeEntry<String, N>> searchGroupNodes(ConstraintNodeMatcher<N> matcher) throws Exception {
List<GroupSearchResult> results;
if (matcher instanceof StandardNodeMatchers.TypeEquals) {
NodeType<? extends N> type = ((StandardNodeMatchers.TypeEquals<N>) matcher).getType();
results = this.client.groups().searchNodesByType(convertNodeType(type)).execute().body();
} else {
Constraint<String> constraint = matcher.getConstraint();
Comparison comparison = constraint.comparison();
String value = constraint.value();
if (comparison == Comparison.EQUAL) {
results = this.client.groups().searchNodesByKey(value).execute().body();
} else if (comparison == Comparison.SIMILAR) {
long wildcards = value.chars().filter(ch -> ch == '%').count();
if (wildcards == 0) {
results = this.client.groups().searchNodesByKey(value).execute().body();
} else if (wildcards == 1 && value.endsWith("%")) {
results = this.client.groups().searchNodesByKeyStartsWith(value.substring(0, value.length() - 1)).execute().body();
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
} else {
throw new UnsupportedOperationException("Unsupported constraint: " + constraint);
}
}
if (results == null) {
throw new IllegalStateException("Client returned null results");
}
List<NodeEntry<String, N>> held = new ArrayList<>();
for (GroupSearchResult result : results) {
String name = result.name();
for (net.luckperms.rest.model.Node node : result.results()) {
N match = matcher.filterConstraintMatch(convertNode(node));
if (match != null) {
held.add(NodeEntry.of(name, match));
}
}
}
return held;
}
@Override
public Track createAndLoadTrack(String name) throws Exception {
net.luckperms.rest.model.Track remoteTrack = this.client.tracks().create(new CreateTrackRequest(name)).execute().body();
if (remoteTrack == null) {
remoteTrack = this.client.tracks().get(name).execute().body();
if (remoteTrack == null) {
throw new IllegalStateException("Unable to create track: " + name);
}
}
Track track = this.plugin.getTrackManager().getOrMake(name);
track.setGroups(remoteTrack.groups());
return track;
}
@Override
public Optional<Track> loadTrack(String name) throws Exception {
net.luckperms.rest.model.Track remoteTrack = this.client.tracks().get(name).execute().body();
if (remoteTrack == null) {
return Optional.empty();
}
Track track = this.plugin.getTrackManager().getOrMake(name);
track.setGroups(remoteTrack.groups());
return Optional.of(track);
}
@Override
public void loadAllTracks() throws Exception {
Set<String> tracks = this.client.tracks().list().execute().body();
if (tracks == null) {
throw new IllegalStateException("Client returned a null list of tracks");
}
if (!Iterators.tryIterate(tracks, this::loadTrack)) {
throw new RuntimeException("Exception occurred whilst loading a track");
}
this.plugin.getTrackManager().retainAll(tracks);
}
@Override
public void saveTrack(Track track) throws Exception {
this.client.tracks().update(track.getName(), new UpdateTrackRequest(track.getGroups())).execute();
}
@Override
public void deleteTrack(Track track) throws Exception {
this.client.tracks().delete(track.getName()).execute();
}
@Override
public PlayerSaveResult savePlayerData(UUID uniqueId, String username) throws Exception {
net.luckperms.rest.model.PlayerSaveResult remoteResult = this.client.users().create(new CreateUserRequest(uniqueId, username)).execute().body();
Set<PlayerSaveResult.Outcome> outcomes = remoteResult.outcomes().stream().map(outcome -> {
switch (outcome) {
case CLEAN_INSERT:
return PlayerSaveResult.Outcome.CLEAN_INSERT;
case NO_CHANGE:
return PlayerSaveResult.Outcome.NO_CHANGE;
case USERNAME_UPDATED:
return PlayerSaveResult.Outcome.USERNAME_UPDATED;
case OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME:
return PlayerSaveResult.Outcome.OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME;
default:
throw new AssertionError(outcome);
}
}).collect(Collectors.toSet());
if (outcomes.isEmpty()) {
throw new IllegalStateException("No outcomes returned");
}
PlayerSaveResultImpl result;
if (outcomes.contains(PlayerSaveResult.Outcome.CLEAN_INSERT)) {
result = PlayerSaveResultImpl.cleanInsert();
} else if (outcomes.contains(PlayerSaveResult.Outcome.NO_CHANGE)) {
result = PlayerSaveResultImpl.noChange();
} else if (outcomes.contains(PlayerSaveResult.Outcome.USERNAME_UPDATED)) {
result = PlayerSaveResultImpl.usernameUpdated(remoteResult.previousUsername());
} else {
throw new IllegalStateException("No base outcome returned");
}
if (outcomes.contains(PlayerSaveResult.Outcome.OTHER_UNIQUE_IDS_PRESENT_FOR_USERNAME)) {
result = result.withOtherUuidsPresent(remoteResult.otherUniqueIds());
}
return result;
}
@Override
public void deletePlayerData(UUID uniqueId) throws Exception {
this.client.users().delete(uniqueId, true).execute();
}
@Override
public @Nullable UUID getPlayerUniqueId(String username) throws Exception {
UserLookupResult result = this.client.users().lookup(username).execute().body();
return result == null ? null : result.uniqueId();
}
@Override
public @Nullable String getPlayerName(UUID uniqueId) throws Exception {
UserLookupResult result = this.client.users().lookup(uniqueId).execute().body();
return result == null ? null : result.username();
}
private static net.luckperms.rest.model.Action convertAction(Action action) {
return new net.luckperms.rest.model.Action(
action.getTimestamp().getEpochSecond(),
new net.luckperms.rest.model.Action.Source(
action.getSource().getUniqueId(),
action.getSource().getName()
),
new net.luckperms.rest.model.Action.Target(
action.getTarget().getUniqueId().orElse(null),
action.getTarget().getName(),
convertActionTargetType(action.getTarget().getType())
),
action.getDescription()
);
}
private static net.luckperms.rest.model.Action.Target.Type convertActionTargetType(Action.Target.Type type) {
switch (type) {
case USER:
return net.luckperms.rest.model.Action.Target.Type.USER;
case GROUP:
return net.luckperms.rest.model.Action.Target.Type.GROUP;
case TRACK:
return net.luckperms.rest.model.Action.Target.Type.TRACK;
default:
throw new AssertionError(type);
}
}
private static LoggedAction convertAction(net.luckperms.rest.model.Action action) {
return LoggedAction.build()
.timestamp(Instant.ofEpochSecond(action.timestamp()))
.source(action.source().uniqueId())
.sourceName(action.source().name())
.target(action.target().uniqueId())
.targetName(action.target().name())
.targetType(convertActionTargetType(action.target().type()))
.description(action.description())
.build();
}
private static Action.Target.Type convertActionTargetType(net.luckperms.rest.model.Action.Target.Type type) {
switch (type) {
case USER:
return Action.Target.Type.USER;
case GROUP:
return Action.Target.Type.GROUP;
case TRACK:
return Action.Target.Type.TRACK;
default:
throw new AssertionError(type);
}
}
private static Node convertNode(net.luckperms.rest.model.Node node) {
NodeBuilder<?, ?> builder = NodeBuilders.determineMostApplicable(node.key());
if (node.value() != null) {
builder.value(node.value());
}
if (node.context() != null) {
builder.context(convertContexts(node.context()));
}
if (node.expiry() != null) {
builder.expiry(node.expiry());
}
return builder.build();
}
private static net.luckperms.rest.model.Node convertNode(Node node) {
return new net.luckperms.rest.model.Node(
node.getKey(),
node.getValue(),
convertContexts(node.getContexts()),
node.getExpiry() == null ? null : node.getExpiry().getEpochSecond()
);
}
public static ContextSet convertContexts(Set<Context> contexts) {
ImmutableContextSetImpl.BuilderImpl builder = new ImmutableContextSetImpl.BuilderImpl();
for (Context context : contexts) {
builder.add(context.key(), context.value());
}
return builder.build();
}
public static Set<Context> convertContexts(ContextSet contexts) {
return StreamSupport.stream(contexts.spliterator(), false)
.map(c -> new Context(c.getKey(), c.getValue()))
.collect(Collectors.toSet());
}
public static net.luckperms.rest.model.NodeType convertNodeType(NodeType<?> type) {
if (type == NodeType.REGEX_PERMISSION) return net.luckperms.rest.model.NodeType.REGEX_PERMISSION;
if (type == NodeType.INHERITANCE) return net.luckperms.rest.model.NodeType.INHERITANCE;
if (type == NodeType.PREFIX) return net.luckperms.rest.model.NodeType.PREFIX;
if (type == NodeType.SUFFIX) return net.luckperms.rest.model.NodeType.SUFFIX;
if (type == NodeType.META) return net.luckperms.rest.model.NodeType.META;
if (type == NodeType.WEIGHT) return net.luckperms.rest.model.NodeType.WEIGHT;
if (type == NodeType.DISPLAY_NAME) return net.luckperms.rest.model.NodeType.DISPLAY_NAME;
throw new IllegalArgumentException("Invalid type: " + type.name());
}
}
@@ -32,24 +32,10 @@ import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
public final class SchemaReader {
private SchemaReader() {}
private static final Pattern CREATE_TABLE_PATTERN = Pattern.compile("^CREATE TABLE [`\"']([^`\"']+)[`\"'].*");
private static final Pattern CREATE_INDEX_PATTERN = Pattern.compile("^CREATE INDEX.* ON [`\"']([^`\"']+)[`\"'].*");
/**
* Parses a schema file to a list of SQL statements
*
* @param is the input stream to read from
* @return a list of statements
* @throws IOException if an error occurs whilst reading the file
*/
public static List<String> getStatements(InputStream is) throws IOException {
List<String> queries = new LinkedList<>();
@@ -67,7 +53,7 @@ public final class SchemaReader {
if (line.endsWith(";")) {
sb.deleteCharAt(sb.length() - 1);
String result = sb.toString().trim().replaceAll(" +", " ");
String result = sb.toString().trim();
if (!result.isEmpty()) {
queries.add(result);
}
@@ -80,29 +66,4 @@ public final class SchemaReader {
return queries;
}
public static String tableFromStatement(String statement) {
Matcher table = CREATE_TABLE_PATTERN.matcher(statement);
if (table.matches()) {
return table.group(1).toLowerCase(Locale.ROOT);
}
Matcher index = CREATE_INDEX_PATTERN.matcher(statement);
if (index.matches()) {
return index.group(1).toLowerCase(Locale.ROOT);
}
throw new IllegalArgumentException("Unknown statement type: " + statement);
}
/**
* Filters which statements should be executed based on the current list of tables in the database
*
* @param statements the statements to filter
* @param currentTables the current tables in the database
* @return the filtered list of statements
*/
public static List<String> filterStatements(List<String> statements, List<String> currentTables) {
return statements.stream()
.filter(statement -> !currentTables.contains(tableFromStatement(statement)))
.collect(Collectors.toList());
}
}
@@ -80,6 +80,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
import java.util.stream.Collectors;
public class SqlStorage implements StorageImplementation {
@@ -136,7 +137,7 @@ public class SqlStorage implements StorageImplementation {
private final LuckPermsPlugin plugin;
private final ConnectionFactory connectionFactory;
private final StatementProcessor statementProcessor;
private final Function<String, String> statementProcessor;
public SqlStorage(LuckPermsPlugin plugin, ConnectionFactory connectionFactory, String tablePrefix) {
this.plugin = plugin;
@@ -158,7 +159,7 @@ public class SqlStorage implements StorageImplementation {
return this.connectionFactory;
}
public StatementProcessor getStatementProcessor() {
public Function<String, String> getStatementProcessor() {
return this.statementProcessor;
}
@@ -166,32 +167,30 @@ public class SqlStorage implements StorageImplementation {
public void init() throws Exception {
this.connectionFactory.init(this.plugin);
List<String> tables;
boolean tableExists;
try (Connection c = this.connectionFactory.getConnection()) {
tables = listTables(c);
tableExists = tableExists(c, this.statementProcessor.apply("{prefix}user_permissions"));
}
if (!tableExists) {
applySchema();
}
applySchema(tables);
}
private void applySchema(List<String> existingTables) throws IOException, SQLException {
String schemaFileName = "me/lucko/luckperms/schema/" + this.connectionFactory.getImplementationName().toLowerCase(Locale.ROOT) + ".sql";
private void applySchema() throws IOException, SQLException {
List<String> statements;
String schemaFileName = "me/lucko/luckperms/schema/" + this.connectionFactory.getImplementationName().toLowerCase(Locale.ROOT) + ".sql";
try (InputStream is = this.plugin.getBootstrap().getResourceStream(schemaFileName)) {
if (is == null) {
throw new IOException("Couldn't locate schema file for " + this.connectionFactory.getImplementationName());
}
statements = SchemaReader.getStatements(is).stream()
.map(this.statementProcessor::process)
.map(this.statementProcessor)
.collect(Collectors.toList());
}
statements = SchemaReader.filterStatements(statements, existingTables);
if (statements.isEmpty()) {
return;
}
try (Connection connection = this.connectionFactory.getConnection()) {
boolean utf8mb4Unsupported = false;
@@ -241,7 +240,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public void logAction(Action entry) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(ACTION_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(ACTION_INSERT))) {
writeAction(entry, ps);
ps.execute();
}
@@ -289,7 +288,7 @@ public class SqlStorage implements StorageImplementation {
try (Connection c = this.connectionFactory.getConnection()) {
if (bulkUpdate.getDataType().isIncludingUsers()) {
StatementProcessor tableReplacement = s -> s.replace("{table}", "{prefix}user_permissions");
Function<String, String> tableReplacement = s -> s.replace("{table}", "{prefix}user_permissions");
BulkUpdateSqlBuilder sqlBuilder = new BulkUpdateSqlBuilder();
sqlBuilder.visit(bulkUpdate);
@@ -320,7 +319,7 @@ public class SqlStorage implements StorageImplementation {
}
if (bulkUpdate.getDataType().isIncludingGroups()) {
StatementProcessor tableReplacement = s -> s.replace("{table}", "{prefix}group_permissions");
Function<String, String> tableReplacement = s -> s.replace("{table}", "{prefix}group_permissions");
BulkUpdateSqlBuilder sqlBuilder = new BulkUpdateSqlBuilder();
sqlBuilder.visit(bulkUpdate);
@@ -449,7 +448,7 @@ public class SqlStorage implements StorageImplementation {
public Set<UUID> getUniqueUsers() throws SQLException {
Set<UUID> uuids = new HashSet<>();
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(USER_PERMISSIONS_SELECT_DISTINCT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_SELECT_DISTINCT))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
UUID uuid = Uuids.fromString(rs.getString("uuid"));
@@ -497,7 +496,7 @@ public class SqlStorage implements StorageImplementation {
public Group createAndLoadGroup(String name) throws SQLException {
String query = GROUP_INSERT.getOrDefault(this.connectionFactory.getImplementationName(), GROUP_INSERT_DEFAULT);
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(query))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(query))) {
ps.setString(1, name);
ps.execute();
}
@@ -560,7 +559,7 @@ public class SqlStorage implements StorageImplementation {
try (Connection c = this.connectionFactory.getConnection()) {
deleteGroupPermissions(c, group.getName());
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_DELETE))) {
ps.setString(1, group.getName());
ps.execute();
}
@@ -663,7 +662,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public void deleteTrack(Track track) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_DELETE))) {
ps.setString(1, track.getName());
ps.execute();
}
@@ -680,7 +679,7 @@ public class SqlStorage implements StorageImplementation {
try (Connection c = this.connectionFactory.getConnection()) {
SqlPlayerData existingPlayerData = selectPlayerData(c, uniqueId);
if (existingPlayerData == null) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_INSERT))) {
ps.setString(1, uniqueId.toString());
ps.setString(2, username);
ps.setString(3, GroupManager.DEFAULT_GROUP_NAME);
@@ -689,7 +688,7 @@ public class SqlStorage implements StorageImplementation {
} else {
oldUsername = existingPlayerData.username;
if (!username.equals(oldUsername)) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_UPDATE_USERNAME_FOR_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_UPDATE_USERNAME_FOR_UUID))) {
ps.setString(1, username);
ps.setString(2, uniqueId.toString());
ps.execute();
@@ -702,7 +701,7 @@ public class SqlStorage implements StorageImplementation {
Set<UUID> conflicting = new HashSet<>();
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_ALL_UUIDS_BY_USERNAME))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_ALL_UUIDS_BY_USERNAME))) {
ps.setString(1, username);
ps.setString(2, uniqueId.toString());
try (ResultSet rs = ps.executeQuery()) {
@@ -716,7 +715,7 @@ public class SqlStorage implements StorageImplementation {
if (!conflicting.isEmpty()) {
// remove the mappings for conflicting uuids
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_DELETE_ALL_UUIDS_BY_USERNAME))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_DELETE_ALL_UUIDS_BY_USERNAME))) {
ps.setString(1, username);
ps.setString(2, uniqueId.toString());
ps.execute();
@@ -731,7 +730,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public void deletePlayerData(UUID uniqueId) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_DELETE))) {
ps.setString(1, uniqueId.toString());
ps.execute();
}
@@ -742,7 +741,7 @@ public class SqlStorage implements StorageImplementation {
public UUID getPlayerUniqueId(String username) throws SQLException {
username = username.toLowerCase(Locale.ROOT);
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_UUID_BY_USERNAME))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_UUID_BY_USERNAME))) {
ps.setString(1, username);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -757,7 +756,7 @@ public class SqlStorage implements StorageImplementation {
@Override
public String getPlayerName(UUID uniqueId) throws SQLException {
try (Connection c = this.connectionFactory.getConnection()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_USERNAME_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_USERNAME_BY_UUID))) {
ps.setString(1, uniqueId.toString());
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -873,7 +872,7 @@ public class SqlStorage implements StorageImplementation {
}
if (!deleteRows.isEmpty()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(deleteSpecificQuery))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(deleteSpecificQuery))) {
for (Long id : deleteRows) {
ps.setLong(1, id);
ps.addBatch();
@@ -882,7 +881,7 @@ public class SqlStorage implements StorageImplementation {
}
}
if (!deleteNodes.isEmpty()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(deleteQuery))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(deleteQuery))) {
for (Node node : deleteNodes) {
ps.setString(1, holder);
writeNode(node, ps);
@@ -894,7 +893,7 @@ public class SqlStorage implements StorageImplementation {
}
if (!add.isEmpty()) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(insertQuery))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(insertQuery))) {
for (Node node : add) {
ps.setString(1, holder);
writeNode(node, ps);
@@ -907,7 +906,7 @@ public class SqlStorage implements StorageImplementation {
private List<Node> selectUserPermissions(Connection c, UUID user) throws SQLException {
List<Node> nodes = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(USER_PERMISSIONS_SELECT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_SELECT))) {
ps.setString(1, user.toString());
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -922,7 +921,7 @@ public class SqlStorage implements StorageImplementation {
}
private SqlPlayerData selectPlayerData(Connection c, UUID user) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_BY_UUID))) {
ps.setString(1, user.toString());
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -983,15 +982,15 @@ public class SqlStorage implements StorageImplementation {
// we don't want to use preparedstatements because the parameter length is variable
// safe to do string concat/replacement because the UUID.toString value isn't injectable
return this.statementProcessor.process(baseQuery) + param;
return this.statementProcessor.apply(baseQuery) + param;
}
private void deleteUser(Connection c, UUID user) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(USER_PERMISSIONS_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(USER_PERMISSIONS_DELETE))) {
ps.setString(1, user.toString());
ps.execute();
}
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
ps.setString(1, GroupManager.DEFAULT_GROUP_NAME);
ps.setString(2, user.toString());
ps.execute();
@@ -1000,7 +999,7 @@ public class SqlStorage implements StorageImplementation {
private void insertPlayerData(Connection c, UUID user, SqlPlayerData data) throws SQLException {
boolean hasPrimaryGroupSaved;
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_SELECT_PRIMARY_GROUP_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_SELECT_PRIMARY_GROUP_BY_UUID))) {
ps.setString(1, user.toString());
try (ResultSet rs = ps.executeQuery()) {
hasPrimaryGroupSaved = rs.next();
@@ -1009,14 +1008,14 @@ public class SqlStorage implements StorageImplementation {
if (hasPrimaryGroupSaved) {
// update
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_UPDATE_PRIMARY_GROUP_BY_UUID))) {
ps.setString(1, data.primaryGroup);
ps.setString(2, user.toString());
ps.execute();
}
} else {
// insert
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(PLAYER_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(PLAYER_INSERT))) {
ps.setString(1, user.toString());
ps.setString(2, data.username);
ps.setString(3, data.primaryGroup);
@@ -1027,7 +1026,7 @@ public class SqlStorage implements StorageImplementation {
private Set<String> selectGroups(Connection c) throws SQLException {
Set<String> groups = new HashSet<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_SELECT_ALL))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_SELECT_ALL))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
groups.add(rs.getString("name").toLowerCase(Locale.ROOT));
@@ -1039,7 +1038,7 @@ public class SqlStorage implements StorageImplementation {
private List<Node> selectGroupPermissions(Connection c, String group) throws SQLException {
List<Node> nodes = new ArrayList<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_PERMISSIONS_SELECT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_SELECT))) {
ps.setString(1, group);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
@@ -1054,7 +1053,7 @@ public class SqlStorage implements StorageImplementation {
}
private void selectAllGroupPermissions(Map<String, Collection<Node>> nodes, Connection c) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_PERMISSIONS_SELECT_ALL))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_SELECT_ALL))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
String holder = rs.getString("name");
@@ -1071,7 +1070,7 @@ public class SqlStorage implements StorageImplementation {
}
private void deleteGroupPermissions(Connection c, String group) throws SQLException {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(GROUP_PERMISSIONS_DELETE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(GROUP_PERMISSIONS_DELETE))) {
ps.setString(1, group);
ps.execute();
}
@@ -1079,7 +1078,7 @@ public class SqlStorage implements StorageImplementation {
private List<String> selectTrack(Connection c, String name) throws SQLException {
String groups;
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_SELECT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_SELECT))) {
ps.setString(1, name);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) {
@@ -1094,7 +1093,7 @@ public class SqlStorage implements StorageImplementation {
private void insertTrack(Connection c, String name, List<String> groups) throws SQLException {
String json = GsonProvider.normal().toJson(groups);
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_INSERT))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_INSERT))) {
ps.setString(1, name);
ps.setString(2, json);
ps.execute();
@@ -1103,7 +1102,7 @@ public class SqlStorage implements StorageImplementation {
private void updateTrack(Connection c, String name, List<String> groups) throws SQLException {
String json = GsonProvider.normal().toJson(groups);
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_UPDATE))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_UPDATE))) {
ps.setString(1, json);
ps.setString(2, name);
ps.execute();
@@ -1112,7 +1111,7 @@ public class SqlStorage implements StorageImplementation {
private Set<String> selectTracks(Connection c) throws SQLException {
Set<String> tracks = new HashSet<>();
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.process(TRACK_SELECT_ALL))) {
try (PreparedStatement ps = c.prepareStatement(this.statementProcessor.apply(TRACK_SELECT_ALL))) {
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
tracks.add(rs.getString("name").toLowerCase(Locale.ROOT));
@@ -1122,14 +1121,15 @@ public class SqlStorage implements StorageImplementation {
return tracks;
}
private static List<String> listTables(Connection connection) throws SQLException {
List<String> tables = new ArrayList<>();
private static boolean tableExists(Connection connection, String table) throws SQLException {
try (ResultSet rs = connection.getMetaData().getTables(connection.getCatalog(), null, "%", null)) {
while (rs.next()) {
tables.add(rs.getString(3).toLowerCase(Locale.ROOT));
if (rs.getString(3).equalsIgnoreCase(table)) {
return true;
}
}
return false;
}
return tables;
}
private static final class SqlPlayerData {
@@ -1,43 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.storage.implementation.sql;
import java.util.Objects;
public interface StatementProcessor {
StatementProcessor USE_BACKTICKS = s -> s.replace('\'', '`');
StatementProcessor USE_DOUBLE_QUOTES = s -> s.replace('\'', '"');
String process(String statement);
default StatementProcessor compose(StatementProcessor before) {
Objects.requireNonNull(before);
return s -> process(before.process(s));
}
}
@@ -25,13 +25,12 @@
package me.lucko.luckperms.common.storage.implementation.sql.builder;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
public class PreparedStatementBuilder {
private final StringBuilder sb = new StringBuilder();
@@ -57,10 +56,10 @@ public class PreparedStatementBuilder {
return this;
}
public PreparedStatement build(Connection connection, StatementProcessor processor) throws SQLException {
public PreparedStatement build(Connection connection, Function<String, String> mapping) throws SQLException {
PreparedStatement statement = null;
try {
statement = connection.prepareStatement(processor.process(this.sb.toString()));
statement = connection.prepareStatement(mapping.apply(this.sb.toString()));
for (int i = 0; i < this.variables.size(); i++) {
String var = this.variables.get(i);
statement.setString(i + 1, var);
@@ -27,10 +27,10 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.StorageMetadata;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.function.Function;
public interface ConnectionFactory {
@@ -42,7 +42,7 @@ public interface ConnectionFactory {
StorageMetadata getMeta();
StatementProcessor getStatementProcessor();
Function<String, String> getStatementProcessor();
Connection getConnection() throws SQLException;
@@ -27,7 +27,6 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection.file;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.io.IOException;
import java.lang.reflect.Constructor;
@@ -39,14 +38,9 @@ import java.sql.Statement;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Properties;
import java.util.function.Function;
public class H2ConnectionFactory extends FlatfileConnectionFactory {
public static final StatementProcessor STATEMENT_PROCESSOR = s -> s
.replace('\'', '`')
.replace("LIKE", "ILIKE")
.replace("value", "`value`")
.replace("``value``", "`value`");
private Constructor<?> connectionConstructor;
public H2ConnectionFactory(Path file) {
@@ -97,8 +91,11 @@ public class H2ConnectionFactory extends FlatfileConnectionFactory {
}
@Override
public StatementProcessor getStatementProcessor() {
return STATEMENT_PROCESSOR;
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`')
.replace("LIKE", "ILIKE")
.replace("value", "`value`")
.replace("``value``", "`value`");
}
/**
@@ -27,7 +27,6 @@ package me.lucko.luckperms.common.storage.implementation.sql.connection.file;
import me.lucko.luckperms.common.dependencies.Dependency;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import java.lang.reflect.Constructor;
import java.nio.file.Path;
@@ -35,6 +34,7 @@ import java.sql.Connection;
import java.sql.SQLException;
import java.util.EnumSet;
import java.util.Properties;
import java.util.function.Function;
public class SqliteConnectionFactory extends FlatfileConnectionFactory {
private Constructor<?> connectionConstructor;
@@ -74,7 +74,7 @@ public class SqliteConnectionFactory extends FlatfileConnectionFactory {
}
@Override
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_BACKTICKS;
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`');
}
}
@@ -25,9 +25,10 @@
package me.lucko.luckperms.common.storage.implementation.sql.connection.hikari;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import me.lucko.luckperms.common.storage.misc.StorageCredentials;
import java.util.function.Function;
public class MariaDbConnectionFactory extends DriverBasedHikariConnectionFactory {
public MariaDbConnectionFactory(StorageCredentials configuration) {
super(configuration);
@@ -54,7 +55,7 @@ public class MariaDbConnectionFactory extends DriverBasedHikariConnectionFactory
}
@Override
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_BACKTICKS;
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`'); // use backticks for quotes
}
}
@@ -25,10 +25,10 @@
package me.lucko.luckperms.common.storage.implementation.sql.connection.hikari;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import me.lucko.luckperms.common.storage.misc.StorageCredentials;
import java.util.Map;
import java.util.function.Function;
public class MySqlConnectionFactory extends DriverBasedHikariConnectionFactory {
public MySqlConnectionFactory(StorageCredentials configuration) {
@@ -80,7 +80,7 @@ public class MySqlConnectionFactory extends DriverBasedHikariConnectionFactory {
}
@Override
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_BACKTICKS;
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '`'); // use backticks for quotes
}
}
@@ -25,10 +25,10 @@
package me.lucko.luckperms.common.storage.implementation.sql.connection.hikari;
import me.lucko.luckperms.common.storage.implementation.sql.StatementProcessor;
import me.lucko.luckperms.common.storage.misc.StorageCredentials;
import java.util.Map;
import java.util.function.Function;
public class PostgresConnectionFactory extends DriverBasedHikariConnectionFactory {
public PostgresConnectionFactory(StorageCredentials configuration) {
@@ -65,7 +65,7 @@ public class PostgresConnectionFactory extends DriverBasedHikariConnectionFactor
}
@Override
public StatementProcessor getStatementProcessor() {
return StatementProcessor.USE_DOUBLE_QUOTES;
public Function<String, String> getStatementProcessor() {
return s -> s.replace('\'', '"');
}
}
@@ -1,70 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.treeview;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerTask;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
public class AsyncPermissionRegistry extends PermissionRegistry implements AutoCloseable {
/** A queue of permission strings to be added to the tree */
private final Queue<String> queue;
/** The tick task */
private final SchedulerTask task;
public AsyncPermissionRegistry(SchedulerAdapter scheduler) {
this.queue = new ConcurrentLinkedQueue<>();
this.task = scheduler.asyncRepeating(this::tick, 1, TimeUnit.SECONDS);
}
@Override
public void offer(String permission) {
if (permission == null) {
throw new NullPointerException("permission");
}
this.queue.offer(permission);
}
private void tick() {
for (String e; (e = this.queue.poll()) != null; ) {
try {
doInsert(e);
} catch (Exception ex) {
// ignore
}
}
}
@Override
public void close() {
this.task.cancel();
}
}
@@ -26,20 +26,35 @@
package me.lucko.luckperms.common.treeview;
import com.google.common.base.Splitter;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerAdapter;
import me.lucko.luckperms.common.plugin.scheduler.SchedulerTask;
import me.lucko.luckperms.common.util.ImmutableCollectors;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;
/**
* Stores a collection of all permissions known to the platform.
*/
public class PermissionRegistry {
public class PermissionRegistry implements AutoCloseable {
private static final Splitter DOT_SPLIT = Splitter.on('.').omitEmptyStrings();
/** The root node in the tree */
private final TreeNode rootNode = new TreeNode();
private final TreeNode rootNode;
/** A queue of permission strings to be added to the tree */
private final Queue<String> queue;
/** The tick task */
private final SchedulerTask task;
public PermissionRegistry(SchedulerAdapter scheduler) {
this.rootNode = new TreeNode();
this.queue = new ConcurrentLinkedQueue<>();
this.task = scheduler.asyncRepeating(this::tick, 1, TimeUnit.SECONDS);
}
public TreeNode getRootNode() {
return this.rootNode;
@@ -51,33 +66,33 @@ public class PermissionRegistry {
.collect(ImmutableCollectors.toList());
}
/**
* Offer a permission to the registry (to be potentially inserted asynchronously).
*
* @param permission the permission
*/
public void offer(String permission) {
insert(permission);
}
/**
* Insert a permission into the registry.
*
* @param permission the permission
*/
public void insert(String permission) {
if (permission == null) {
throw new NullPointerException("permission");
}
this.queue.offer(permission);
}
try {
doInsert(permission);
} catch (Exception ex) {
// ignore
private void tick() {
for (String e; (e = this.queue.poll()) != null; ) {
insert(e);
}
}
protected void doInsert(String permission) {
@Override
public void close() {
this.task.cancel();
}
public void insert(String permission) {
try {
doInsert(permission);
} catch (Exception ex) {
ex.printStackTrace();
}
}
private void doInsert(String permission) {
permission = permission.toLowerCase(Locale.ROOT);
// split the permission up into parts
@@ -29,7 +29,7 @@ import me.lucko.luckperms.common.locale.TranslationManager;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.TranslatableComponent;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer;
import java.time.Duration;
import java.time.temporal.ChronoUnit;
@@ -72,7 +72,7 @@ public class DurationFormatter {
* @return the formatted string
*/
public String formatString(Duration duration) {
return PlainTextComponentSerializer.plainText().serialize(TranslationManager.render(format(duration)));
return PlainComponentSerializer.plain().serialize(TranslationManager.render(format(duration)));
}
/**
@@ -25,21 +25,36 @@
package me.lucko.luckperms.common.util;
import java.util.Collections;
import java.util.Set;
import com.github.benmanes.caffeine.cache.Cache;
import java.util.concurrent.TimeUnit;
public final class ExpiringSet {
private ExpiringSet() {}
/**
* A simple expiring set implementation using Caffeine caches
*
* @param <E> element type
*/
public class ExpiringSet<E> {
private final Cache<E, Long> cache;
private final long lifetime;
/**
* An expiring set using Caffeine caches
*
* @param <E> the element type
* @return a new expiring set
*/
public static <E> Set<E> newExpiringSet(long duration, TimeUnit unit) {
return Collections.newSetFromMap(CaffeineFactory.newBuilder().expireAfterWrite(duration, unit).<E, Boolean>build().asMap());
public ExpiringSet(long duration, TimeUnit unit) {
this.cache = CaffeineFactory.newBuilder().expireAfterWrite(duration, unit).build();
this.lifetime = unit.toMillis(duration);
}
public boolean add(E item) {
boolean present = contains(item);
this.cache.put(item, System.currentTimeMillis() + this.lifetime);
return !present;
}
public boolean contains(E item) {
Long timeout = this.cache.getIfPresent(item);
return timeout != null && timeout > System.currentTimeMillis();
}
public void remove(E item) {
this.cache.invalidate(item);
}
}
@@ -43,7 +43,6 @@ import me.lucko.luckperms.common.verbose.event.PermissionCheckEvent;
import me.lucko.luckperms.common.verbose.event.VerboseEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.JoinConfiguration;
import net.kyori.adventure.text.event.HoverEvent;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.Node;
@@ -223,7 +222,7 @@ public class VerboseListener {
}
// send the message
HoverEvent<Component> hoverEvent = HoverEvent.showText(Component.join(JoinConfiguration.newlines(), hover));
HoverEvent<Component> hoverEvent = HoverEvent.showText(Component.join(Component.newline(), hover));
this.notifiedSender.sendMessage(component.hoverEvent(hoverEvent));
}
@@ -32,75 +32,14 @@ import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
/**
* The signature algorithm & public/private key crypto logic used by the web editor socket connection.
* Utilities for public/private key crypto used by the web editor socket connection.
*/
public enum SignatureAlgorithm {
V1_RSA(1, "RSA", "SHA256withRSA") {
@Override
public KeyPair generateKeyPair() {
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(4096);
return generator.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException("Exception generating keypair", e);
}
}
},
V2_ECDSA(2, "EC", "SHA256withECDSAinP1363Format") {
@Override
public KeyPair generateKeyPair() {
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
generator.initialize(new ECGenParameterSpec("secp256r1"));
return generator.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException("Exception generating keypair", e);
}
}
};
/**
* The selected {@link SignatureAlgorithm} for the current environment.
*/
public static final SignatureAlgorithm INSTANCE;
static {
// select an instance to use based on the available algorithms
SignatureAlgorithm instance = V1_RSA;
try {
KeyPairGenerator.getInstance(V2_ECDSA.keyFactoryAlgorithm);
Signature.getInstance(V2_ECDSA.signatureAlgorithm);
instance = V2_ECDSA;
} catch (Exception e) {
// ignore
}
INSTANCE = instance;
}
private final int protocolVersion;
private final String keyFactoryAlgorithm;
private final String signatureAlgorithm;
SignatureAlgorithm(int protocolVersion, String keyFactoryAlgorithm, String signatureAlgorithm) {
this.protocolVersion = protocolVersion;
this.keyFactoryAlgorithm = keyFactoryAlgorithm;
this.signatureAlgorithm = signatureAlgorithm;
}
/**
* Gets the corresponding protocol version
*
* @return the protocol version
*/
public int protocolVersion() {
return this.protocolVersion;
}
public final class CryptographyUtils {
private CryptographyUtils() {}
/**
* Parse a public key from the given string.
@@ -109,11 +48,11 @@ public enum SignatureAlgorithm {
* @return the parsed public key
* @throws IllegalArgumentException if the input was invalid
*/
public PublicKey parsePublicKey(String base64String) throws IllegalArgumentException {
public static PublicKey parsePublicKey(String base64String) throws IllegalArgumentException {
try {
byte[] bytes = Base64.getDecoder().decode(base64String);
X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);
KeyFactory rsa = KeyFactory.getInstance(this.keyFactoryAlgorithm);
KeyFactory rsa = KeyFactory.getInstance("RSA");
return rsa.generatePublic(spec);
} catch (Exception e) {
throw new IllegalArgumentException("Exception parsing public key", e);
@@ -125,7 +64,15 @@ public enum SignatureAlgorithm {
*
* @return the generated key pair
*/
public abstract KeyPair generateKeyPair();
public static KeyPair generateKeyPair() {
try {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(4096);
return generator.generateKeyPair();
} catch (Exception e) {
throw new RuntimeException("Exception generating keypair", e);
}
}
/**
* Signs {@code msg} using the given {@link PrivateKey}.
@@ -134,9 +81,9 @@ public enum SignatureAlgorithm {
* @param msg the message
* @return a base64 string containing the signature
*/
public String sign(PrivateKey privateKey, String msg) {
public static String sign(PrivateKey privateKey, String msg) {
try {
Signature sign = Signature.getInstance(this.signatureAlgorithm);
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(msg.getBytes(StandardCharsets.UTF_8));
@@ -155,9 +102,9 @@ public enum SignatureAlgorithm {
* @param signatureBase64 the provided signature
* @return true if the signature is ok
*/
public boolean verify(PublicKey publicKey, String msg, String signatureBase64) {
public static boolean verify(PublicKey publicKey, String msg, String signatureBase64) {
try {
Signature sign = Signature.getInstance(this.signatureAlgorithm);
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initVerify(publicKey);
sign.update(msg.getBytes(StandardCharsets.UTF_8));
@@ -47,6 +47,8 @@ import java.util.concurrent.TimeoutException;
public class WebEditorSocket {
private static final int PROTOCOL_VERSION = 1;
/** The plugin */
private final LuckPermsPlugin plugin;
/** The sender who created the editor session */
@@ -112,7 +114,7 @@ public class WebEditorSocket {
String publicKey = Base64.getEncoder().encodeToString(this.pluginKeyPair.getPublic().getEncoded());
JsonObject socket = new JsonObject();
socket.addProperty("protocolVersion", SignatureAlgorithm.INSTANCE.protocolVersion());
socket.addProperty("protocolVersion", PROTOCOL_VERSION);
socket.addProperty("channelId", channelId);
socket.addProperty("publicKey", publicKey);
@@ -130,7 +132,7 @@ public class WebEditorSocket {
*/
public void send(JsonObject msg) {
String encoded = GsonProvider.normal().toJson(msg);
String signature = SignatureAlgorithm.INSTANCE.sign(this.pluginKeyPair.getPrivate(), encoded);
String signature = CryptographyUtils.sign(this.pluginKeyPair.getPrivate(), encoded);
JsonObject frame = new JObject()
.add("msg", encoded)
@@ -27,7 +27,7 @@ package me.lucko.luckperms.common.webeditor.socket.listener;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.locale.Message;
import me.lucko.luckperms.common.webeditor.socket.SignatureAlgorithm;
import me.lucko.luckperms.common.webeditor.socket.CryptographyUtils;
import me.lucko.luckperms.common.webeditor.socket.SocketMessageType;
import me.lucko.luckperms.common.webeditor.socket.WebEditorSocket;
import me.lucko.luckperms.common.webeditor.store.RemoteSession;
@@ -73,7 +73,7 @@ public class HandlerHello implements Handler {
String nonce = getStringOrThrow(msg, "nonce");
String sessionId = getStringOrThrow(msg, "sessionId");
String browser = msg.get("browser").getAsString();
PublicKey remotePublicKey = SignatureAlgorithm.INSTANCE.parsePublicKey(msg.get("publicKey").getAsString());
PublicKey remotePublicKey = CryptographyUtils.parsePublicKey(msg.get("publicKey").getAsString());
// check if the public keys are the same
// (this allows the same editor to re-connect, but prevents new connections)
@@ -27,7 +27,7 @@ package me.lucko.luckperms.common.webeditor.socket.listener;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.util.gson.GsonProvider;
import me.lucko.luckperms.common.webeditor.socket.SignatureAlgorithm;
import me.lucko.luckperms.common.webeditor.socket.CryptographyUtils;
import me.lucko.luckperms.common.webeditor.socket.SocketMessageType;
import me.lucko.luckperms.common.webeditor.socket.WebEditorSocket;
import okhttp3.Response;
@@ -126,7 +126,7 @@ public class WebEditorSocketListener extends WebSocketListener {
// check signature to ensure the message is from the connected editor
PublicKey remotePublicKey = this.socket.getRemotePublicKey();
boolean verified = remotePublicKey != null && SignatureAlgorithm.INSTANCE.verify(remotePublicKey, innerMsg, signature);
boolean verified = remotePublicKey != null && CryptographyUtils.verify(remotePublicKey, innerMsg, signature);
// parse the inner message
JsonObject msg = GsonProvider.parser().parse(innerMsg).getAsJsonObject();
@@ -29,7 +29,7 @@ import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import me.lucko.luckperms.common.config.ConfigKeys;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.webeditor.socket.SignatureAlgorithm;
import me.lucko.luckperms.common.webeditor.socket.CryptographyUtils;
import java.security.KeyPair;
import java.util.concurrent.CompletableFuture;
@@ -51,7 +51,7 @@ public class WebEditorStore {
this.keystore = new WebEditorKeystore(plugin.getBootstrap().getConfigDirectory().resolve("editor-keystore.json"));
Supplier<CompletableFuture<KeyPair>> keyPair = () -> CompletableFuture.supplyAsync(
SignatureAlgorithm.INSTANCE::generateKeyPair,
CryptographyUtils::generateKeyPair,
plugin.getBootstrap().getScheduler().async()
);
@@ -14,7 +14,6 @@ luckperms.commandsystem.command-not-recognised=Command not recognised
luckperms.commandsystem.no-permission=You do not have permission to use this command!
luckperms.commandsystem.no-permission-subcommands=You do not have permission to use any sub commands
luckperms.commandsystem.already-executing-command=Another command is being executed, waiting for it to finish...
luckperms.commandsystem.commands-disabled=LuckPerms commands are disabled
luckperms.commandsystem.usage.sub-commands-header=Sub Commands
luckperms.commandsystem.usage.usage-header=Command Usage
luckperms.commandsystem.usage.arguments-header=Arguments
@@ -1,168 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata;
import com.google.common.collect.ListMultimap;
import me.lucko.luckperms.common.cacheddata.metastack.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.cacheddata.result.IntegerResult;
import me.lucko.luckperms.common.cacheddata.result.StringResult;
import me.lucko.luckperms.common.cacheddata.type.MetaAccumulator;
import me.lucko.luckperms.common.node.types.DisplayName;
import me.lucko.luckperms.common.node.types.Meta;
import me.lucko.luckperms.common.node.types.Prefix;
import me.lucko.luckperms.common.node.types.Suffix;
import me.lucko.luckperms.common.node.types.Weight;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
import net.luckperms.api.metastacking.MetaStackDefinition;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.types.MetaNode;
import net.luckperms.api.node.types.PrefixNode;
import net.luckperms.api.node.types.SuffixNode;
import net.luckperms.api.node.types.WeightNode;
import org.junit.jupiter.api.Test;
import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
import java.util.SortedMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class MetaAccumulatorTest {
@Test
public void testStates() {
SimpleMetaStackDefinition definition = new SimpleMetaStackDefinition(ImmutableList.of(StandardStackElements.HIGHEST), DuplicateRemovalFunction.RETAIN_ALL, "", "", "");
MetaAccumulator accumulator = new MetaAccumulator(definition, definition);
assertThrows(IllegalStateException.class, accumulator::getMeta);
assertThrows(IllegalStateException.class, () -> accumulator.getChatMeta(ChatMetaType.PREFIX));
assertThrows(IllegalStateException.class, accumulator::getPrefixes);
assertThrows(IllegalStateException.class, accumulator::getSuffixes);
assertThrows(IllegalStateException.class, accumulator::getWeight);
assertThrows(IllegalStateException.class, accumulator::getPrimaryGroup);
assertThrows(IllegalStateException.class, accumulator::getPrefixDefinition);
assertThrows(IllegalStateException.class, accumulator::getSuffixDefinition);
assertThrows(IllegalStateException.class, accumulator::getPrefix);
assertThrows(IllegalStateException.class, accumulator::getSuffix);
Prefix prefixNode = Prefix.builder("hello", 100).build();
accumulator.accumulateNode(prefixNode);
accumulator.complete();
assertThrows(IllegalStateException.class, () -> accumulator.accumulateNode(prefixNode));
StringResult<PrefixNode> prefixResult = accumulator.getPrefix();
assertEquals(prefixNode, prefixResult.node());
}
@Test
public void testEmpty() {
SimpleMetaStackDefinition definition = new SimpleMetaStackDefinition(ImmutableList.of(StandardStackElements.HIGHEST), DuplicateRemovalFunction.RETAIN_ALL, "[", "|", "]");
MetaAccumulator accumulator = new MetaAccumulator(definition, definition);
accumulator.complete();
ListMultimap<String, StringResult<MetaNode>> meta = accumulator.getMeta();
assertEquals(0, meta.size());
SortedMap<Integer, StringResult<PrefixNode>> prefixes = accumulator.getPrefixes();
assertEquals(0, prefixes.size());
SortedMap<Integer, StringResult<SuffixNode>> suffixes = accumulator.getSuffixes();
assertEquals(0, suffixes.size());
IntegerResult<WeightNode> weight = accumulator.getWeight();
assertTrue(weight.isNull());
assertEquals(0, weight.intResult());
assertNull(weight.node());
String primaryGroup = accumulator.getPrimaryGroup();
assertNull(primaryGroup);
MetaStackDefinition prefixDefinition = accumulator.getPrefixDefinition();
assertSame(definition, prefixDefinition);
MetaStackDefinition suffixDefinition = accumulator.getSuffixDefinition();
assertSame(definition, suffixDefinition);
StringResult<PrefixNode> prefix = accumulator.getPrefix();
assertNull(prefix.result());
assertNull(prefix.node());
StringResult<SuffixNode> suffix = accumulator.getSuffix();
assertNull(suffix.result());
assertNull(suffix.node());
}
@Test
public void testSimple() {
SimpleMetaStackDefinition definition = new SimpleMetaStackDefinition(ImmutableList.of(StandardStackElements.HIGHEST), DuplicateRemovalFunction.RETAIN_ALL, "[", "|", "]");
MetaAccumulator accumulator = new MetaAccumulator(definition, definition);
accumulator.accumulateNode(Prefix.builder("b", 90).build());
accumulator.accumulateNode(Prefix.builder("a", 100).build());
accumulator.accumulateNode(Prefix.builder("c", 80).build());
accumulator.accumulateNode(Suffix.builder("foo", 80).build());
accumulator.accumulateNode(Meta.builder().key("foo").value("bar").build());
accumulator.accumulateNode(Weight.builder(10).build()); // ignored
accumulator.accumulateNode(DisplayName.builder("hello").build());
accumulator.accumulateWeight(IntegerResult.of(Weight.builder(5).build()));
accumulator.setPrimaryGroup("member");
accumulator.complete();
StringResult<PrefixNode> prefix = accumulator.getPrefix();
assertEquals("[a]", prefix.result());
SortedMap<Integer, StringResult<PrefixNode>> prefixes = accumulator.getPrefixes();
assertEquals(ImmutableSet.of(100, 90, 80), prefixes.keySet());
StringResult<SuffixNode> suffix = accumulator.getSuffix();
assertEquals("[foo]", suffix.result());
SortedMap<Integer, StringResult<SuffixNode>> suffixes = accumulator.getSuffixes();
assertEquals(ImmutableSet.of(80), suffixes.keySet());
ListMultimap<String, StringResult<MetaNode>> meta = accumulator.getMeta();
assertEquals(3, meta.size());
assertEquals(ImmutableSet.of("foo", "weight", "primarygroup"), meta.keySet());
assertEquals("bar", meta.get("foo").get(0).result());
assertEquals("5", meta.get("weight").get(0).result());
assertEquals("member", meta.get("primarygroup").get(0).result());
IntegerResult<WeightNode> weight = accumulator.getWeight();
assertEquals(5, weight.intResult());
String primaryGroup = accumulator.getPrimaryGroup();
assertEquals("member", primaryGroup);
}
}
@@ -1,104 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata;
import me.lucko.luckperms.common.cacheddata.metastack.SimpleMetaStackDefinition;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.cacheddata.result.StringResult;
import me.lucko.luckperms.common.cacheddata.type.MetaStackAccumulator;
import me.lucko.luckperms.common.node.types.Prefix;
import net.luckperms.api.metastacking.DuplicateRemovalFunction;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.types.PrefixNode;
import org.junit.jupiter.api.Test;
import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
public class MetaStackAccumulatorTest {
@Test
public void testEmpty() {
SimpleMetaStackDefinition definition = new SimpleMetaStackDefinition(ImmutableList.of(StandardStackElements.HIGHEST), DuplicateRemovalFunction.RETAIN_ALL, "", "", "");
MetaStackAccumulator<PrefixNode> accumulator = new MetaStackAccumulator<>(definition, ChatMetaType.PREFIX);
StringResult<PrefixNode> result = accumulator.toResult();
assertNotNull(result);
assertNull(result.result());
assertNull(result.node());
assertNull(result.overriddenResult());
String formattedString = accumulator.toFormattedString();
assertNull(formattedString);
}
@Test
public void testSingle() {
SimpleMetaStackDefinition definition = new SimpleMetaStackDefinition(ImmutableList.of(StandardStackElements.HIGHEST), DuplicateRemovalFunction.RETAIN_ALL, "[", "|", "]");
MetaStackAccumulator<PrefixNode> accumulator = new MetaStackAccumulator<>(definition, ChatMetaType.PREFIX);
PrefixNode a = Prefix.builder("a", 100).build();
PrefixNode b = Prefix.builder("b", 90).build();
PrefixNode c = Prefix.builder("c", 80).build();
accumulator.offer(b);
accumulator.offer(a);
accumulator.offer(c);
StringResult<PrefixNode> result = accumulator.toResult();
assertNotNull(result);
assertEquals("[a]", result.result());
assertEquals(a, result.node());
assertNull(result.overriddenResult());
}
@Test
public void testMultiple() {
SimpleMetaStackDefinition definition = new SimpleMetaStackDefinition(ImmutableList.of(StandardStackElements.LOWEST, StandardStackElements.HIGHEST), DuplicateRemovalFunction.RETAIN_ALL, "[", "|", "]");
MetaStackAccumulator<PrefixNode> accumulator = new MetaStackAccumulator<>(definition, ChatMetaType.PREFIX);
PrefixNode a = Prefix.builder("a", 100).build();
PrefixNode b = Prefix.builder("b", 90).build();
PrefixNode c = Prefix.builder("c", 80).build();
accumulator.offer(b);
accumulator.offer(a);
accumulator.offer(c);
StringResult<PrefixNode> result = accumulator.toResult();
assertNotNull(result);
assertEquals("[c|a]", result.result());
assertEquals(c, result.node());
StringResult<PrefixNode> overriddenResult = result.overriddenResult();
assertNotNull(overriddenResult);
assertEquals(a, overriddenResult.node());
assertNull(overriddenResult.overriddenResult());
}
}
@@ -1,243 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata;
import me.lucko.luckperms.common.cacheddata.metastack.StandardStackElements;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.model.InheritanceOrigin;
import me.lucko.luckperms.common.model.PermissionHolderIdentifier;
import me.lucko.luckperms.common.model.Track;
import me.lucko.luckperms.common.model.manager.track.StandardTrackManager;
import me.lucko.luckperms.common.model.manager.track.TrackManager;
import me.lucko.luckperms.common.node.types.Prefix;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import net.luckperms.api.metastacking.MetaStackElement;
import net.luckperms.api.model.data.DataType;
import net.luckperms.api.node.ChatMetaType;
import net.luckperms.api.node.metadata.types.InheritanceOriginMetadata;
import org.junit.jupiter.api.Test;
import org.testcontainers.shaded.com.google.common.collect.ImmutableList;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class MetaStackElementTest {
@Test
public void testHighest() {
MetaStackElement highest = StandardStackElements.HIGHEST;
assertTrue(highest.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).build(), // node
null // current
));
assertFalse(highest.shouldAccumulate(
ChatMetaType.SUFFIX,
Prefix.builder("foo", 100).build(), // node
null // current
));
assertTrue(highest.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).build(), // node
Prefix.builder("bar", 50).build()) // current
);
assertFalse(highest.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 50).build(), // node
Prefix.builder("bar", 100).build()) // current
);
}
@Test
public void testLowest() {
MetaStackElement lowest = StandardStackElements.LOWEST;
assertTrue(lowest.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).build(), // node
null // current
));
assertFalse(lowest.shouldAccumulate(
ChatMetaType.SUFFIX,
Prefix.builder("foo", 100).build(), // node
null // current
));
assertTrue(lowest.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 50).build(), // node
Prefix.builder("bar", 100).build()) // current
);
assertFalse(lowest.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).build(), // node
Prefix.builder("bar", 50).build()) // current
);
}
@Test
public void testHighestOwn() {
InheritanceOrigin userOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.USER, ""), DataType.NORMAL);
InheritanceOrigin groupOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.GROUP, ""), DataType.NORMAL);
MetaStackElement highestOwn = StandardStackElements.HIGHEST_OWN;
assertTrue(highestOwn.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, userOrigin).build(), // node
null // current
));
assertFalse(highestOwn.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, groupOrigin).build(), // node
null // current
));
}
@Test
public void testHighestInherited() {
InheritanceOrigin userOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.USER, ""), DataType.NORMAL);
InheritanceOrigin groupOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.GROUP, ""), DataType.NORMAL);
MetaStackElement highestInherited = StandardStackElements.HIGHEST_INHERITED;
assertTrue(highestInherited.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, groupOrigin).build(), // node
null // current
));
assertFalse(highestInherited.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, userOrigin).build(), // node
null // current
));
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Test
public void testHighestFromGroupOnTrack() {
LuckPermsPlugin plugin = mock(LuckPermsPlugin.class);
TrackManager<Track> trackManager = new StandardTrackManager(plugin);
when(plugin.getTrackManager()).thenReturn((TrackManager) trackManager);
Track track = trackManager.getOrMake("test");
track.setGroups(ImmutableList.of("foo", "bar"));
InheritanceOrigin fooOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.GROUP, "foo"), DataType.NORMAL);
InheritanceOrigin bazOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.GROUP, "baz"), DataType.NORMAL);
MetaStackElement highestFromGroupOnTrack = StandardStackElements.highestFromGroupOnTrack(plugin, "test");
assertTrue(highestFromGroupOnTrack.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, fooOrigin).build(), // node
null // current
));
assertFalse(highestFromGroupOnTrack.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, bazOrigin).build(), // node
null // current
));
}
@SuppressWarnings({"unchecked", "rawtypes"})
@Test
public void testHighestNotFromGroupOnTrack() {
LuckPermsPlugin plugin = mock(LuckPermsPlugin.class);
TrackManager<Track> trackManager = new StandardTrackManager(plugin);
when(plugin.getTrackManager()).thenReturn((TrackManager) trackManager);
Track track = trackManager.getOrMake("test");
track.setGroups(ImmutableList.of("foo", "bar"));
InheritanceOrigin fooOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.GROUP, "foo"), DataType.NORMAL);
InheritanceOrigin bazOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.GROUP, "baz"), DataType.NORMAL);
InheritanceOrigin userOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.USER, "foo"), DataType.NORMAL);
MetaStackElement highestNotFromGroupOnTrack = StandardStackElements.highestNotFromGroupOnTrack(plugin, "test");
assertTrue(highestNotFromGroupOnTrack.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, userOrigin).build(), // node
null // current
));
assertTrue(highestNotFromGroupOnTrack.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, bazOrigin).build(), // node
null // current
));
assertFalse(highestNotFromGroupOnTrack.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, fooOrigin).build(), // node
null // current
));
}
@Test
public void testHighestFromGroup() {
InheritanceOrigin fooOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.GROUP, "foo"), DataType.NORMAL);
InheritanceOrigin bazOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.GROUP, "baz"), DataType.NORMAL);
InheritanceOrigin userOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.USER, "foo"), DataType.NORMAL);
MetaStackElement highestFromGroup = StandardStackElements.highestFromGroup("foo");
assertTrue(highestFromGroup.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, fooOrigin).build(), // node
null // current
));
assertFalse(highestFromGroup.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, bazOrigin).build(), // node
null // current
));
assertFalse(highestFromGroup.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, userOrigin).build(), // node
null // current
));
}
@Test
public void testHighestNotFromGroup() {
InheritanceOrigin fooOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.GROUP, "foo"), DataType.NORMAL);
InheritanceOrigin bazOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.GROUP, "baz"), DataType.NORMAL);
InheritanceOrigin userOrigin = new InheritanceOrigin(new PermissionHolderIdentifier(HolderType.USER, "foo"), DataType.NORMAL);
MetaStackElement highestNotFromGroup = StandardStackElements.highestNotFromGroup("foo");
assertTrue(highestNotFromGroup.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, userOrigin).build(), // node
null // current
));
assertTrue(highestNotFromGroup.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, bazOrigin).build(), // node
null // current
));
assertFalse(highestNotFromGroup.shouldAccumulate(
ChatMetaType.PREFIX,
Prefix.builder("foo", 100).withMetadata(InheritanceOriginMetadata.KEY, fooOrigin).build(), // node
null // current
));
}
}
@@ -1,81 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import me.lucko.luckperms.common.cacheddata.result.StringResult;
import me.lucko.luckperms.common.cacheddata.type.SimpleMetaValueSelector;
import me.lucko.luckperms.common.cacheddata.type.SimpleMetaValueSelector.Strategy;
import net.luckperms.api.cacheddata.Result;
import net.luckperms.api.node.types.MetaNode;
import org.junit.jupiter.api.Test;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
public class MetaValueSelectorTest {
@Test
public void testStrategies() {
Map<String, Strategy> strategies = ImmutableMap.of(
"foo", Strategy.HIGHEST_NUMBER,
"bar", Strategy.LOWEST_NUMBER
);
SimpleMetaValueSelector selector = new SimpleMetaValueSelector(strategies, Strategy.INHERITANCE);
// empty
assertThrows(IllegalArgumentException.class, () -> selector.selectValue("hello", ImmutableList.of()));
Result<String, MetaNode> foo = StringResult.of("foo");
// single value
Result<String, MetaNode> value = selector.selectValue("abc", ImmutableList.of(foo));
assertSame(foo, value);
// fallback to default when values are not numbers
value = selector.selectValue("foo", ImmutableList.of(foo));
assertSame(foo, value);
Result<String, MetaNode> one = StringResult.of("1");
Result<String, MetaNode> two = StringResult.of("2");
Result<String, MetaNode> three = StringResult.of("3");
ImmutableList<Result<String, MetaNode>> values = ImmutableList.of(two, one, three);
// first value
assertSame(two, selector.selectValue("abc", values));
// highest value
assertSame(three, selector.selectValue("foo", values));
// lowest value
assertSame(one, selector.selectValue("bar", values));
}
}
@@ -1,54 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.cacheddata;
import org.junit.jupiter.api.Test;
import java.util.concurrent.TimeUnit;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class UsageTrackedTest {
@Test
public void testUsedRecently() {
TestUsageTracked usageTracked = new TestUsageTracked();
assertTrue(usageTracked.usedInTheLast(1, TimeUnit.MINUTES));
usageTracked.recordUsage();
assertTrue(usageTracked.usedInTheLast(1, TimeUnit.MINUTES));
usageTracked.setLastUsed(System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1));
assertFalse(usageTracked.usedInTheLast(1, TimeUnit.MINUTES));
}
static final class TestUsageTracked extends UsageTracked {
void setLastUsed(long lastUsed) {
this.lastUsed = lastUsed;
}
}
}
@@ -1,83 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.command.access;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.testcontainers.shaded.com.google.common.collect.ImmutableSet;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class CommandPermissionTest {
private static final ImmutableSet<CommandPermission> ALLOWED_READ_ONLY = ImmutableSet.of(
CommandPermission.SYNC,
CommandPermission.INFO,
CommandPermission.EDITOR,
CommandPermission.VERBOSE,
CommandPermission.TREE,
CommandPermission.SEARCH,
CommandPermission.EXPORT,
CommandPermission.RELOAD_CONFIG,
CommandPermission.TRANSLATIONS,
CommandPermission.LIST_GROUPS,
CommandPermission.LIST_TRACKS,
CommandPermission.USER_INFO,
CommandPermission.USER_PERM_INFO,
CommandPermission.USER_PERM_CHECK,
CommandPermission.USER_PARENT_INFO,
CommandPermission.USER_META_INFO,
CommandPermission.USER_EDITOR,
CommandPermission.USER_SHOW_TRACKS,
CommandPermission.GROUP_INFO,
CommandPermission.GROUP_PERM_INFO,
CommandPermission.GROUP_PERM_CHECK,
CommandPermission.GROUP_PARENT_INFO,
CommandPermission.GROUP_META_INFO,
CommandPermission.GROUP_EDITOR,
CommandPermission.GROUP_LIST_MEMBERS,
CommandPermission.GROUP_SHOW_TRACKS,
CommandPermission.TRACK_INFO,
CommandPermission.TRACK_EDITOR,
CommandPermission.LOG_RECENT,
CommandPermission.LOG_USER_HISTORY,
CommandPermission.LOG_GROUP_HISTORY,
CommandPermission.LOG_TRACK_HISTORY,
CommandPermission.LOG_SEARCH,
CommandPermission.LOG_NOTIFY,
CommandPermission.SPONGE_PERMISSION_INFO,
CommandPermission.SPONGE_PARENT_INFO,
CommandPermission.SPONGE_OPTION_INFO
);
@ParameterizedTest
@EnumSource(CommandPermission.class)
public void testReadOnly(CommandPermission permission) {
String name = permission.name();
assertEquals(ALLOWED_READ_ONLY.contains(permission), permission.isReadOnly(), name);
}
}
@@ -1,76 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.config;
import me.lucko.luckperms.common.config.generic.adapter.FileSecretConfigAdapter;
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
import me.lucko.luckperms.common.plugin.logging.PluginLogger;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.lenient;
@ExtendWith(MockitoExtension.class)
public class FileSecretConfigAdapterTest {
@Mock private LuckPermsPlugin plugin;
@Mock private PluginLogger logger;
@BeforeEach
public void setupMocks() {
lenient().when(this.plugin.getLogger()).thenReturn(this.logger);
}
@Test
public void testRead(@TempDir Path directory) throws IOException {
Path path = directory.resolve("luckperms_server");
Files.write(path, "test".getBytes());
FileSecretConfigAdapter adapter = new FileSecretConfigAdapter(this.plugin, directory.toString());
String server = adapter.getString("server", null);
assertEquals("test", server);
}
@Test
public void testNoOp() throws IOException {
FileSecretConfigAdapter adapter = new FileSecretConfigAdapter(this.plugin, null);
String server = adapter.getString("server", null);
assertNull(server);
}
}
@@ -78,21 +78,6 @@ public class FilterMongoTest {
),
// {"$or": [{"foo": "hello"}, {"bar": "world"}]}
"{\"$or\": [{\"foo\": \"hello\"}, {\"bar\": \"world\"}]}"
),
Arguments.of(
FilterList.or(
TestField.FOO.isEqualTo("hello", ConstraintFactory.STRINGS),
TestField.BAR.isNotEqualTo("world", ConstraintFactory.STRINGS),
TestField.BAZ.isSimilarTo("abc%xyz", ConstraintFactory.STRINGS),
TestField.BAZ.isNotSimilarTo("a_c", ConstraintFactory.STRINGS)
),
// {"$or": [
// {"foo": "hello"},
// {"bar": {"$ne": "world"}},
// {"baz": {"$regularExpression": {"pattern": "abc.*xyz", "options": "i"}}},
// {"baz": {"$not": {"$regularExpression": {"pattern": "a.c", "options": "i"}}}}
// ]}
"{\"$or\": [{\"foo\": \"hello\"}, {\"bar\": {\"$ne\": \"world\"}}, {\"baz\": {\"$regularExpression\": {\"pattern\": \"abc.*xyz\", \"options\": \"i\"}}}, {\"baz\": {\"$not\": {\"$regularExpression\": {\"pattern\": \"a.c\", \"options\": \"i\"}}}}]}"
)
);
}
@@ -109,7 +94,7 @@ public class FilterMongoTest {
}
private enum TestField implements FilterField<Object, String> {
FOO, BAR, BAZ;
FOO, BAR;
@Override
public String getValue(Object object) {
@@ -73,16 +73,6 @@ public class FilterSqlTest {
),
" WHERE foo = hello OR bar = world",
" WHERE foo = ? OR bar = ?"
),
Arguments.of(
FilterList.or(
TestField.FOO.isEqualTo("hello", ConstraintFactory.STRINGS),
TestField.BAR.isNotEqualTo("world", ConstraintFactory.STRINGS),
TestField.BAZ.isSimilarTo("abc%xyz", ConstraintFactory.STRINGS),
TestField.BAZ.isNotSimilarTo("a_c", ConstraintFactory.STRINGS)
),
" WHERE foo = hello OR bar != world OR baz LIKE abc%xyz OR baz NOT LIKE a_c",
" WHERE foo = ? OR bar != ? OR baz LIKE ? OR baz NOT LIKE ?"
)
);
}
@@ -90,7 +80,7 @@ public class FilterSqlTest {
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource
public void testQueries(FilterList<Object> filters, String expectedSql, String expectedSqlParams) {
FilterSqlBuilder<Object> sqlBuilder = new TestFilterSqlBuilder();
TestFilterMongoBuilder sqlBuilder = new TestFilterMongoBuilder();
sqlBuilder.visit(filters);
System.out.println(sqlBuilder.builder().toReadableString());
@@ -101,7 +91,7 @@ public class FilterSqlTest {
}
private enum TestField implements FilterField<Object, String> {
FOO, BAR, BAZ;
FOO, BAR;
@Override
public String getValue(Object object) {
@@ -114,7 +104,7 @@ public class FilterSqlTest {
}
}
private static final class TestFilterSqlBuilder extends FilterSqlBuilder<Object> {
private static final class TestFilterMongoBuilder extends FilterSqlBuilder<Object> {
@Override
public void visitFieldName(FilterField<Object, ?> field) {
@@ -1,116 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.messaging;
import com.google.gson.JsonObject;
import me.lucko.luckperms.common.actionlog.ActionJsonSerializer;
import me.lucko.luckperms.common.actionlog.LoggedAction;
import me.lucko.luckperms.common.messaging.message.ActionLogMessageImpl;
import me.lucko.luckperms.common.messaging.message.CustomMessageImpl;
import me.lucko.luckperms.common.messaging.message.UpdateMessageImpl;
import me.lucko.luckperms.common.messaging.message.UserUpdateMessageImpl;
import me.lucko.luckperms.common.util.gson.JObject;
import net.luckperms.api.actionlog.Action;
import org.junit.jupiter.api.Test;
import java.time.Instant;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class MessageImplTest {
@Test
public void testUpdateMessage() {
UUID uuid = UUID.fromString("22f9e168-8815-44f1-83c8-b642ebfbcef2");
// encode
UpdateMessageImpl msg = new UpdateMessageImpl(uuid);
assertEquals("{\"id\":\"22f9e168-8815-44f1-83c8-b642ebfbcef2\",\"type\":\"update\"}", msg.asEncodedString());
// decode
msg = UpdateMessageImpl.decode(new JsonObject(), uuid);
assertEquals(uuid, msg.getId());
}
@Test
public void testUserUpdateMessage() {
UUID uuid = UUID.fromString("22f9e168-8815-44f1-83c8-b642ebfbcef2");
UUID userUuid = UUID.fromString("4c087cd9-f444-4c52-8438-e03e57ba2e8e");
// encode
UserUpdateMessageImpl msg = new UserUpdateMessageImpl(uuid, userUuid);
// {"id":"22f9e168-8815-44f1-83c8-b642ebfbcef2","type":"userupdate","content":{"userUuid":"4c087cd9-f444-4c52-8438-e03e57ba2e8e"}}
assertEquals("{\"id\":\"22f9e168-8815-44f1-83c8-b642ebfbcef2\",\"type\":\"userupdate\",\"content\":{\"userUuid\":\"4c087cd9-f444-4c52-8438-e03e57ba2e8e\"}}", msg.asEncodedString());
// decode
msg = UserUpdateMessageImpl.decode(new JObject().add("userUuid", userUuid.toString()).toJson(), uuid);
assertEquals(uuid, msg.getId());
assertEquals(userUuid, msg.getUserUniqueId());
}
@Test
public void testActionLogMessage() {
UUID uuid = UUID.fromString("22f9e168-8815-44f1-83c8-b642ebfbcef2");
LoggedAction action = LoggedAction.build()
.source(UUID.fromString("d3500320-564c-436e-87a0-026d7f2c92f6"))
.sourceName("Test")
.targetType(Action.Target.Type.GROUP)
.targetName("test")
.description("test")
.timestamp(Instant.ofEpochSecond(1))
.build();
// encode
ActionLogMessageImpl msg = new ActionLogMessageImpl(uuid, action);
// {"id":"22f9e168-8815-44f1-83c8-b642ebfbcef2","type":"log","content":{"timestamp":1,"source":{"uniqueId":"d3500320-564c-436e-87a0-026d7f2c92f6","name":"Test"},"target":{"type":"GROUP","name":"test"},"description":"test"}}
assertEquals("{\"id\":\"22f9e168-8815-44f1-83c8-b642ebfbcef2\",\"type\":\"log\",\"content\":{\"timestamp\":1,\"source\":{\"uniqueId\":\"d3500320-564c-436e-87a0-026d7f2c92f6\",\"name\":\"Test\"},\"target\":{\"type\":\"GROUP\",\"name\":\"test\"},\"description\":\"test\"}}", msg.asEncodedString());
// decode
msg = ActionLogMessageImpl.decode(ActionJsonSerializer.serialize(action), uuid);
assertEquals(uuid, msg.getId());
assertEquals(action, msg.getAction());
}
@Test
public void testCustomMessage() {
UUID uuid = UUID.fromString("22f9e168-8815-44f1-83c8-b642ebfbcef2");
String channelId = "test";
String payload = "test";
// encode
CustomMessageImpl msg = new CustomMessageImpl(uuid, channelId, payload);
// {"id":"22f9e168-8815-44f1-83c8-b642ebfbcef2","type":"custom","content":{"channelId":"test","payload":"test"}}
assertEquals("{\"id\":\"22f9e168-8815-44f1-83c8-b642ebfbcef2\",\"type\":\"custom\",\"content\":{\"channelId\":\"test\",\"payload\":\"test\"}}", msg.asEncodedString());
// decode
msg = CustomMessageImpl.decode(new JObject().add("channelId", channelId).add("payload", payload).toJson(), uuid);
assertEquals(uuid, msg.getId());
assertEquals(channelId, msg.getChannelId());
assertEquals(payload, msg.getPayload());
}
}
@@ -1,71 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.node;
import me.lucko.luckperms.common.model.HolderType;
import me.lucko.luckperms.common.node.factory.NodeCommandFactory;
import me.lucko.luckperms.common.node.types.Inheritance;
import me.lucko.luckperms.common.node.types.Meta;
import me.lucko.luckperms.common.node.types.Permission;
import me.lucko.luckperms.common.node.types.Prefix;
import net.luckperms.api.node.Node;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.concurrent.TimeUnit;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class NodeCommandFactoryTest {
private static Stream<Arguments> testUndoCommand() {
return Stream.of(
Arguments.of("group test permission unset test", Permission.builder().permission("test").build(), HolderType.GROUP, false),
Arguments.of("user test permission unset test", Permission.builder().permission("test").build(), HolderType.USER, false),
Arguments.of("user test permission unsettemp test", Permission.builder().permission("test").expiry(1, TimeUnit.HOURS).build(), HolderType.USER, false),
Arguments.of("user test permission unset test server=foo world=bar", Permission.builder().permission("test").withContext("server", "foo").withContext("world", "bar").build(), HolderType.USER, false),
Arguments.of("user test permission unset test global", Permission.builder().permission("test").build(), HolderType.USER, true),
Arguments.of("user test parent remove test", Inheritance.builder().group("test").build(), HolderType.USER, false),
Arguments.of("user test parent removetemp test", Inheritance.builder().group("test").expiry(1, TimeUnit.HOURS).build(), HolderType.USER, false),
Arguments.of("user test meta removeprefix 100 test", Prefix.builder().priority(100).prefix("test").build(), HolderType.USER, false),
Arguments.of("user test meta removetempprefix 100 test", Prefix.builder().priority(100).prefix("test").expiry(1, TimeUnit.HOURS).build(), HolderType.USER, false),
Arguments.of("user test meta removeprefix 100 \"hello world\"", Prefix.builder().priority(100).prefix("hello world").build(), HolderType.USER, false),
Arguments.of("user test meta unset foo", Meta.builder().key("foo").value("bar").build(), HolderType.USER, false),
Arguments.of("user test meta unsettemp foo", Meta.builder().key("foo").value("bar").expiry(1, TimeUnit.HOURS).build(), HolderType.USER, false)
);
}
@ParameterizedTest(name = "[{index}] {0}")
@MethodSource
public void testUndoCommand(String expected, Node node, HolderType holderType, boolean explicitGlobalContext) {
String result = NodeCommandFactory.undoCommand(node, "test", holderType, explicitGlobalContext);
assertEquals(expected, result);
}
}
@@ -1,111 +0,0 @@
/*
* This file is part of LuckPerms, licensed under the MIT License.
*
* Copyright (c) lucko (Luck) <luck@lucko.me>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package me.lucko.luckperms.common.node;
import me.lucko.luckperms.common.filter.Comparison;
import me.lucko.luckperms.common.filter.Constraint;
import me.lucko.luckperms.common.node.matcher.ConstraintNodeMatcher;
import me.lucko.luckperms.common.node.matcher.StandardNodeMatchers;
import me.lucko.luckperms.common.node.types.Permission;
import net.luckperms.api.node.Node;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.node.types.MetaNode;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class NodeMatcherTest {
@Test
public void testKey() {
ConstraintNodeMatcher<Node> matcher = StandardNodeMatchers.key("foo");
Constraint<String> constraint = matcher.getConstraint();
assertEquals(Comparison.EQUAL, constraint.comparison());
assertEquals("foo", constraint.value());
ConstraintNodeMatcher<Permission> typedMatcher = StandardNodeMatchers.key(Permission.builder().permission("foo").build());
Constraint<String> typedConstraint = typedMatcher.getConstraint();
assertEquals(Comparison.EQUAL, typedConstraint.comparison());
assertEquals("foo", typedConstraint.value());
}
@ParameterizedTest
@CsvSource({
"foo, foo%",
})
public void testKeyStartsWith(String value, String expectedConstraint) {
ConstraintNodeMatcher<Node> matcher = StandardNodeMatchers.keyStartsWith(value);
Constraint<String> constraint = matcher.getConstraint();
assertEquals(Comparison.SIMILAR, constraint.comparison());
assertEquals(expectedConstraint, constraint.value());
}
@ParameterizedTest
@CsvSource({
"foo, meta.foo.%",
})
public void testMetaKey(String value, String expectedConstraint) {
ConstraintNodeMatcher<MetaNode> matcher = StandardNodeMatchers.metaKey(value);
Constraint<String> constraint = matcher.getConstraint();
assertEquals(Comparison.SIMILAR, constraint.comparison());
assertEquals(expectedConstraint, constraint.value());
}
private static Stream<Arguments> testType() {
return Stream.of(
Arguments.of(NodeType.REGEX_PERMISSION, "r=%"),
Arguments.of(NodeType.INHERITANCE, "group.%"),
Arguments.of(NodeType.PREFIX, "prefix.%.%"),
Arguments.of(NodeType.SUFFIX, "suffix.%.%"),
Arguments.of(NodeType.META, "meta.%.%"),
Arguments.of(NodeType.WEIGHT, "weight.%"),
Arguments.of(NodeType.DISPLAY_NAME, "displayname.%")
);
}
@ParameterizedTest
@MethodSource
public void testType(NodeType<?> type, String expectedValue) {
ConstraintNodeMatcher<?> matcher = StandardNodeMatchers.type(type);
Constraint<String> constraint = matcher.getConstraint();
assertEquals(Comparison.SIMILAR, constraint.comparison());
assertEquals(expectedValue, constraint.value());
}
}
@@ -139,9 +139,7 @@ public class NodeParseTest {
"prefix.0.hello, 0, hello",
"prefix.100.hello world, 100, hello world",
"prefix.100.HELLO world &123, 100, HELLO world &123",
"prefix.100., 100, ''",
"prefix.100.hello\\.world, 100, hello.world",
"prefix.100.hello.world, 100, hello.world",
"prefix.100., 100, ''"
})
public void testPrefix(String key, int expectedPriority, String expectedValue) {
Prefix.Builder builder = Prefix.parse(key);
@@ -159,8 +157,7 @@ public class NodeParseTest {
"prefix.",
"prefix.hello",
"prefix.100",
"prefix.hello.hello",
"suffix.100.hello"
"prefix.hello.hello"
})
public void testPrefixFail(String key) {
Prefix.Builder builder = Prefix.parse(key);
@@ -174,9 +171,7 @@ public class NodeParseTest {
"suffix.0.hello, 0, hello",
"suffix.100.hello world, 100, hello world",
"suffix.100.HELLO world &123, 100, HELLO world &123",
"suffix.100., 100, ''",
"suffix.100.hello\\.world, 100, hello.world",
"suffix.100.hello.world, 100, hello.world",
"suffix.100., 100, ''"
})
public void testSuffix(String key, int expectedPriority, String expectedValue) {
Suffix.Builder builder = Suffix.parse(key);
@@ -194,8 +189,7 @@ public class NodeParseTest {
"suffix.",
"suffix.hello",
"suffix.100",
"suffix.hello.hello",
"prefix.100.hello"
"suffix.hello.hello"
})
public void testSuffixFail(String key) {
Suffix.Builder builder = Suffix.parse(key);
@@ -206,10 +200,7 @@ public class NodeParseTest {
@CsvSource({
"meta.k.v, k, v",
"meta.hello.world, hello, world",
"meta.hello., hello, ''",
"meta.a\\.b.hel\\.lo, a.b, hel.lo",
"meta.a\\\\.b.hel\\.lo, a\\.b, hel.lo",
"meta.a.b.c, a, b.c"
"meta.hello., hello, ''"
})
public void testMeta(String key, String expectedKey, String expectedValue) {
Meta.Builder builder = Meta.parse(key);
Loaded 100 of 206 files, more files were not shown because too many files have changed in this diff. Show more