From 5c5d1e8d0acbbcb07b84318d94505c0999393902 Mon Sep 17 00:00:00 2001 From: Luck Date: Fri, 9 Jun 2017 18:20:12 +0100 Subject: [PATCH] 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 --- .../common/core/model/ImmutableNode.java | 5 +- .../luckperms/common/core/model/User.java | 68 ++++---- .../lucko/luckperms/common/data/Importer.java | 3 +- .../lucko/luckperms/common/data/LogEntry.java | 3 +- .../storage/backing/FlatfileBacking.java | 55 +----- .../common/storage/backing/JSONBacking.java | 4 +- .../storage/backing/MongoDBBacking.java | 4 +- .../common/storage/backing/SQLBacking.java | 2 +- .../common/storage/backing/YAMLBacking.java | 4 +- .../storage/backing/utils/FileUuidCache.java | 156 ++++++++++++++++++ .../luckperms/common/utils/DateUtil.java | 6 +- 11 files changed, 221 insertions(+), 89 deletions(-) create mode 100644 common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/FileUuidCache.java diff --git a/common/src/main/java/me/lucko/luckperms/common/core/model/ImmutableNode.java b/common/src/main/java/me/lucko/luckperms/common/core/model/ImmutableNode.java index 78e5a951a..7c464571a 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/model/ImmutableNode.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/model/ImmutableNode.java @@ -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 diff --git a/common/src/main/java/me/lucko/luckperms/common/core/model/User.java b/common/src/main/java/me/lucko/luckperms/common/core/model/User.java index 94edd6c13..caaa5a1b7 100644 --- a/common/src/main/java/me/lucko/luckperms/common/core/model/User.java +++ b/common/src/main/java/me/lucko/luckperms/common/core/model/User.java @@ -95,7 +95,7 @@ public class User extends PermissionHolder implements Identifiable 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 e : uuidCache.entrySet()) { - if (e.getValue().equalsIgnoreCase(uuid.toString())) { - return e.getKey(); - } - } - return null; - } - - private Map getUUIDCache() { - Map 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 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); } } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java index e59e65417..67393cfdd 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/JSONBacking.java @@ -171,10 +171,10 @@ public class JSONBacking extends FlatfileBacking { Set data = deserializePermissions(object.get("permissions").getAsJsonArray()); Set 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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java index fac9fae7b..eb8b7cbbe 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/MongoDBBacking.java @@ -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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java index 9dc0c826e..0bf382f0e 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/SQLBacking.java @@ -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()) { diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java index 9964dea0f..e83ebbc75 100644 --- a/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/YAMLBacking.java @@ -175,10 +175,10 @@ public class YAMLBacking extends FlatfileBacking { Set data = deserializePermissions((List) values.get("permissions")); Set 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; } diff --git a/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/FileUuidCache.java b/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/FileUuidCache.java new file mode 100644 index 000000000..a4126cea7 --- /dev/null +++ b/common/src/main/java/me/lucko/luckperms/common/storage/backing/utils/FileUuidCache.java @@ -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> 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 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> 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 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 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> 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(); + } + } + +} diff --git a/common/src/main/java/me/lucko/luckperms/common/utils/DateUtil.java b/common/src/main/java/me/lucko/luckperms/common/utils/DateUtil.java index 3cfd713ba..4813d3e53 100644 --- a/common/src/main/java/me/lucko/luckperms/common/utils/DateUtil.java +++ b/common/src/main/java/me/lucko/luckperms/common/utils/DateUtil.java @@ -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()); } /**