mirror of
https://github.com/LuckPerms/LuckPerms.git
synced 2024-12-28 20:17:55 +01:00
Fixes some issues with username storage and handling
- Fix the yaml/json UUID cache corrupting username data when performing uuid --> username checks - Fix handling of usernames when users are loaded Closes #301
This commit is contained in:
parent
ff0c988a1d
commit
5c5d1e8d0a
@ -40,6 +40,7 @@ import me.lucko.luckperms.api.context.ContextSet;
|
||||
import me.lucko.luckperms.api.context.ImmutableContextSet;
|
||||
import me.lucko.luckperms.api.context.MutableContextSet;
|
||||
import me.lucko.luckperms.common.core.NodeFactory;
|
||||
import me.lucko.luckperms.common.utils.DateUtil;
|
||||
import me.lucko.luckperms.common.utils.PatternCache;
|
||||
import me.lucko.luckperms.common.utils.ShorthandParser;
|
||||
|
||||
@ -314,12 +315,12 @@ public final class ImmutableNode implements Node {
|
||||
@Override
|
||||
public long getSecondsTilExpiry() {
|
||||
Preconditions.checkState(isTemporary(), "Node does not have an expiry time.");
|
||||
return expireAt - (System.currentTimeMillis() / 1000L);
|
||||
return expireAt - DateUtil.unixSecondsNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasExpired() {
|
||||
return isTemporary() && expireAt < (System.currentTimeMillis() / 1000L);
|
||||
return isTemporary() && expireAt < DateUtil.unixSecondsNow();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -95,7 +95,7 @@ public class User extends PermissionHolder implements Identifiable<UserIdentifie
|
||||
super(uuid.toString(), plugin);
|
||||
this.uuid = uuid;
|
||||
|
||||
setName(name, true);
|
||||
setName(name, false);
|
||||
this.primaryGroup = plugin.getConfiguration().get(ConfigKeys.PRIMARY_GROUP_CALCULATION).apply(this);
|
||||
this.userData = new UserCache(this);
|
||||
getPlugin().getApiProvider().getEventFactory().handleUserCacheLoad(this, userData);
|
||||
@ -110,39 +110,49 @@ public class User extends PermissionHolder implements Identifiable<UserIdentifie
|
||||
return Optional.ofNullable(name);
|
||||
}
|
||||
|
||||
public boolean setName(String name, boolean force) {
|
||||
// if the value being set is null
|
||||
if (name == null || name.equalsIgnoreCase("null") || name.isEmpty()) {
|
||||
// only apply the change if it is being forced
|
||||
if (force) {
|
||||
// if the name is already null, return false
|
||||
if (this.name == null) {
|
||||
return false;
|
||||
} else {
|
||||
// set the new null value
|
||||
this.name = null;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// we already have a non-null value, so return false
|
||||
return false;
|
||||
/**
|
||||
* Sets the users name
|
||||
*
|
||||
* @param name the name to set
|
||||
* @param weak if true, the value will only be updated if a value hasn't been set previously.
|
||||
* @return true if a change was made
|
||||
*/
|
||||
public boolean setName(String name, boolean weak) {
|
||||
|
||||
// if weak is true, only update the value in the User if it's null
|
||||
if (weak && this.name != null) {
|
||||
|
||||
// try to update casing if they're equalIgnoreCase
|
||||
if (name != null && this.name.equalsIgnoreCase(name)) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// consistency. if the name being set is equivalent to null, just make it null.
|
||||
if (name != null && (name.isEmpty() || name.equalsIgnoreCase("null"))) {
|
||||
name = null;
|
||||
}
|
||||
|
||||
// if one or the other is null, just update and return true
|
||||
if ((this.name == null) != (name == null)) {
|
||||
this.name = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.name == null) {
|
||||
// they're both null
|
||||
return false;
|
||||
} else {
|
||||
// the name being set is not null
|
||||
if (this.name == null) {
|
||||
// both non-null
|
||||
if (this.name.equalsIgnoreCase(name)) {
|
||||
this.name = name; // update case anyway, but return false
|
||||
return false;
|
||||
} else {
|
||||
this.name = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
// update the capitalisation, but still return false
|
||||
if (this.name.equalsIgnoreCase(name)) {
|
||||
this.name = name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// completely new value, just set & return true
|
||||
this.name = name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,7 @@ import me.lucko.luckperms.common.constants.Constants;
|
||||
import me.lucko.luckperms.common.constants.Message;
|
||||
import me.lucko.luckperms.common.constants.Permission;
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
import me.lucko.luckperms.common.utils.DateUtil;
|
||||
|
||||
import io.github.mkremins.fanciful.FancyMessage;
|
||||
|
||||
@ -96,7 +97,7 @@ public class Importer implements Runnable {
|
||||
|
||||
int index = 1;
|
||||
for (String command : commands) {
|
||||
long time = System.currentTimeMillis() / 1000;
|
||||
long time = DateUtil.unixSecondsNow();
|
||||
if (lastMsg < (time - PROGRESS_REPORT_SECONDS)) {
|
||||
lastMsg = time;
|
||||
|
||||
|
@ -34,6 +34,7 @@ import me.lucko.luckperms.common.core.model.PermissionHolder;
|
||||
import me.lucko.luckperms.common.core.model.Track;
|
||||
import me.lucko.luckperms.common.core.model.User;
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
import me.lucko.luckperms.common.utils.DateUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ -135,7 +136,7 @@ public class LogEntry extends me.lucko.luckperms.api.LogEntry {
|
||||
@Override
|
||||
public LogEntry build() {
|
||||
if (getTimestamp() == 0L) {
|
||||
super.timestamp(System.currentTimeMillis() / 1000L);
|
||||
super.timestamp(DateUtil.unixSecondsNow());
|
||||
}
|
||||
|
||||
return super.build();
|
||||
|
@ -37,25 +37,18 @@ import me.lucko.luckperms.common.data.Log;
|
||||
import me.lucko.luckperms.common.managers.GroupManager;
|
||||
import me.lucko.luckperms.common.managers.TrackManager;
|
||||
import me.lucko.luckperms.common.plugin.LuckPermsPlugin;
|
||||
import me.lucko.luckperms.common.storage.backing.utils.FileUuidCache;
|
||||
import me.lucko.luckperms.common.storage.backing.utils.LegacyJSONSchemaMigration;
|
||||
import me.lucko.luckperms.common.storage.backing.utils.LegacyYAMLSchemaMigration;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.logging.FileHandler;
|
||||
import java.util.logging.Formatter;
|
||||
import java.util.logging.Level;
|
||||
@ -67,7 +60,7 @@ public abstract class FlatfileBacking extends AbstractBacking {
|
||||
private static final String LOG_FORMAT = "%s(%s): [%s] %s(%s) --> %s";
|
||||
|
||||
private final Logger actionLogger = Logger.getLogger("luckperms_actions");
|
||||
private Map<String, String> uuidCache = new ConcurrentHashMap<>();
|
||||
private FileUuidCache uuidCache = new FileUuidCache();
|
||||
|
||||
private final File pluginDir;
|
||||
|
||||
@ -98,7 +91,7 @@ public abstract class FlatfileBacking extends AbstractBacking {
|
||||
return;
|
||||
}
|
||||
|
||||
uuidCache.putAll(getUUIDCache());
|
||||
uuidCache.load(uuidData);
|
||||
|
||||
try {
|
||||
FileHandler fh = new FileHandler(actionLog.getAbsolutePath(), 0, 1, true);
|
||||
@ -207,7 +200,7 @@ public abstract class FlatfileBacking extends AbstractBacking {
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
saveUUIDCache(uuidCache);
|
||||
uuidCache.save(uuidData);
|
||||
}
|
||||
|
||||
protected void registerFileAction(String type, File file) {
|
||||
@ -325,51 +318,17 @@ public abstract class FlatfileBacking extends AbstractBacking {
|
||||
|
||||
@Override
|
||||
public boolean saveUUIDData(String username, UUID uuid) {
|
||||
username = username.toLowerCase();
|
||||
uuidCache.put(username, uuid.toString());
|
||||
uuidCache.addMapping(username, uuid);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID getUUID(String username) {
|
||||
username = username.toLowerCase();
|
||||
if (uuidCache.get(username) == null) return null;
|
||||
return UUID.fromString(uuidCache.get(username));
|
||||
return uuidCache.lookupUUID(username);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName(UUID uuid) {
|
||||
for (Map.Entry<String, String> e : uuidCache.entrySet()) {
|
||||
if (e.getValue().equalsIgnoreCase(uuid.toString())) {
|
||||
return e.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, String> getUUIDCache() {
|
||||
Map<String, String> cache = new HashMap<>();
|
||||
|
||||
try (BufferedReader reader = Files.newBufferedReader(uuidData.toPath(), StandardCharsets.UTF_8)) {
|
||||
Properties props = new Properties();
|
||||
props.load(reader);
|
||||
for (String key : props.stringPropertyNames()) {
|
||||
cache.put(key, props.getProperty(key));
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return cache;
|
||||
}
|
||||
|
||||
private void saveUUIDCache(Map<String, String> cache) {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(uuidData.toPath(), StandardCharsets.UTF_8)) {
|
||||
Properties properties = new Properties();
|
||||
properties.putAll(cache);
|
||||
properties.store(writer, null);
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return uuidCache.lookupUsername(uuid);
|
||||
}
|
||||
}
|
||||
|
@ -171,10 +171,10 @@ public class JSONBacking extends FlatfileBacking {
|
||||
Set<NodeModel> data = deserializePermissions(object.get("permissions").getAsJsonArray());
|
||||
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
|
||||
user.setNodes(nodes);
|
||||
user.setName(name, true);
|
||||
|
||||
boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false);
|
||||
|
||||
if (user.setName(name, false)) {
|
||||
if (user.getName().isPresent() && (name == null || !user.getName().get().equalsIgnoreCase(name))) {
|
||||
save = true;
|
||||
}
|
||||
|
||||
|
@ -328,10 +328,10 @@ public class MongoDBBacking extends AbstractBacking {
|
||||
.collect(Collectors.toSet())
|
||||
);
|
||||
user.getPrimaryGroup().setStoredValue(d.getString("primaryGroup"));
|
||||
user.setName(name, true);
|
||||
|
||||
boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false);
|
||||
|
||||
if (user.setName(d.getString("name"), false)) {
|
||||
if (user.getName().isPresent() && (name == null || !user.getName().get().equalsIgnoreCase(name))) {
|
||||
save = true;
|
||||
}
|
||||
|
||||
|
@ -348,7 +348,7 @@ public class SQLBacking extends AbstractBacking {
|
||||
user.getPrimaryGroup().setStoredValue(pg);
|
||||
|
||||
// Update their username to what was in the storage if the one in the local instance is null
|
||||
user.setName(userName.get(), false);
|
||||
user.setName(userName.get(), true);
|
||||
|
||||
// If the user has any data in storage
|
||||
if (!data.isEmpty()) {
|
||||
|
@ -175,10 +175,10 @@ public class YAMLBacking extends FlatfileBacking {
|
||||
Set<NodeModel> data = deserializePermissions((List<Object>) values.get("permissions"));
|
||||
Set<Node> nodes = data.stream().map(NodeModel::toNode).collect(Collectors.toSet());
|
||||
user.setNodes(nodes);
|
||||
user.setName(name, true);
|
||||
|
||||
boolean save = plugin.getUserManager().giveDefaultIfNeeded(user, false);
|
||||
|
||||
if (user.setName(name, false)) {
|
||||
if (user.getName().isPresent() && (name == null || !user.getName().get().equalsIgnoreCase(name))) {
|
||||
save = true;
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,156 @@
|
||||
package me.lucko.luckperms.common.storage.backing.utils;
|
||||
|
||||
import com.google.common.base.Splitter;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import me.lucko.luckperms.common.utils.DateUtil;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class FileUuidCache {
|
||||
private static final Splitter KV_SPLIT = Splitter.on('=').omitEmptyStrings();
|
||||
private static final Splitter TIME_SPLIT = Splitter.on('|').omitEmptyStrings();
|
||||
|
||||
// the map for lookups
|
||||
private final Map<String, Map.Entry<UUID, Long>> lookupMap = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Adds a mapping to the cache
|
||||
*
|
||||
* @param username the username of the player
|
||||
* @param uuid the uuid of the player
|
||||
*/
|
||||
public void addMapping(String username, UUID uuid) {
|
||||
lookupMap.put(username.toLowerCase(), Maps.immutableEntry(uuid, DateUtil.unixSecondsNow()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most recent uuid which connected with the given username, or null
|
||||
*
|
||||
* @param username the username to lookup with
|
||||
* @return a uuid, or null
|
||||
*/
|
||||
public UUID lookupUUID(String username) {
|
||||
Map.Entry<UUID, Long> ret = lookupMap.get(username.toLowerCase());
|
||||
return ret == null ? null : ret.getKey();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most recent username used by a given uuid
|
||||
*
|
||||
* @param uuid the uuid to lookup with
|
||||
* @return a username, or null
|
||||
*/
|
||||
public String lookupUsername(UUID uuid) {
|
||||
String username = null;
|
||||
Long time = Long.MIN_VALUE;
|
||||
|
||||
for (Map.Entry<String, Map.Entry<UUID, Long>> ent : lookupMap.entrySet()) {
|
||||
if (!ent.getValue().getKey().equals(uuid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Long t = ent.getValue().getValue();
|
||||
|
||||
if (t > time) {
|
||||
time = t;
|
||||
username = ent.getKey();
|
||||
}
|
||||
}
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
public void load(File file) {
|
||||
if (!file.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try (BufferedReader reader = Files.newBufferedReader(file.toPath(), StandardCharsets.UTF_8)) {
|
||||
|
||||
String entry;
|
||||
while ((entry = reader.readLine()) != null) {
|
||||
entry = entry.trim();
|
||||
if (entry.isEmpty() || entry.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Iterator<String> parts = KV_SPLIT.split(entry).iterator();
|
||||
|
||||
if (!parts.hasNext()) continue;
|
||||
String key = parts.next();
|
||||
|
||||
if (!parts.hasNext()) continue;
|
||||
String value = parts.next();
|
||||
|
||||
UUID uid;
|
||||
Long t;
|
||||
|
||||
// contains a time (backwards compat)
|
||||
if (value.contains("|")) {
|
||||
// try to split and extract the time element from the end.
|
||||
Iterator<String> valueParts = TIME_SPLIT.split(value).iterator();
|
||||
|
||||
if (!valueParts.hasNext()) continue;
|
||||
String uuid = valueParts.next();
|
||||
|
||||
if (!valueParts.hasNext()) continue;
|
||||
String time = valueParts.next();
|
||||
|
||||
try {
|
||||
uid = UUID.fromString(uuid);
|
||||
} catch (IllegalArgumentException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
t = Long.parseLong(time);
|
||||
} catch (NumberFormatException e) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// just parse from the value
|
||||
try {
|
||||
uid = UUID.fromString(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
t = 0L;
|
||||
}
|
||||
|
||||
lookupMap.put(key, Maps.immutableEntry(uid, t));
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void save(File file) {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(file.toPath(), StandardCharsets.UTF_8)) {
|
||||
writer.write("# LuckPerms UUID lookup cache");
|
||||
writer.newLine();
|
||||
|
||||
for (Map.Entry<String, Map.Entry<UUID, Long>> ent : lookupMap.entrySet()) {
|
||||
String out = ent.getKey() + "=" + ent.getValue().getKey().toString() + "|" + ent.getValue().getValue().toString();
|
||||
writer.write(out);
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -46,8 +46,12 @@ public class DateUtil {
|
||||
private static final Pattern TIME_PATTERN = Pattern.compile("(?:([0-9]+)\\s*y[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*mo[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*w[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*d[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*h[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*m[a-z]*[,\\s]*)?" + "(?:([0-9]+)\\s*(?:s[a-z]*)?)?", Pattern.CASE_INSENSITIVE);
|
||||
private static final int MAX_YEARS = 100000;
|
||||
|
||||
public static long unixSecondsNow() {
|
||||
return System.currentTimeMillis() / 1000L;
|
||||
}
|
||||
|
||||
public static boolean shouldExpire(long unixTime) {
|
||||
return unixTime < (System.currentTimeMillis() / 1000);
|
||||
return unixTime < (unixSecondsNow());
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user