diff --git a/pom.xml b/pom.xml index e3f98b3..f5984ee 100644 --- a/pom.xml +++ b/pom.xml @@ -61,7 +61,7 @@ com.sparkjava spark-core - 2.4 + 2.5 com.google.guava diff --git a/src/main/java/net/frozenorb/apiv3/APIv3.java b/src/main/java/net/frozenorb/apiv3/APIv3.java index 17b18dc..36f0dd2 100644 --- a/src/main/java/net/frozenorb/apiv3/APIv3.java +++ b/src/main/java/net/frozenorb/apiv3/APIv3.java @@ -14,10 +14,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.frozenorb.apiv3.actors.ActorType; import net.frozenorb.apiv3.filters.*; -import net.frozenorb.apiv3.models.Grant; -import net.frozenorb.apiv3.models.IPLogEntry; -import net.frozenorb.apiv3.models.Punishment; -import net.frozenorb.apiv3.models.User; +import net.frozenorb.apiv3.models.*; import net.frozenorb.apiv3.routes.GETDump; import net.frozenorb.apiv3.routes.GETWhoAmI; import net.frozenorb.apiv3.routes.NotFound; @@ -48,20 +45,25 @@ import net.frozenorb.apiv3.serialization.ObjectIdTypeAdapter; import net.frozenorb.apiv3.unsorted.BugsnagSLF4JLogger; import net.frozenorb.apiv3.unsorted.LoggingExceptionHandler; import net.frozenorb.apiv3.utils.IPUtils; +import net.frozenorb.apiv3.utils.UUIDUtils; import org.bson.Document; import org.bson.types.ObjectId; import org.mongodb.morphia.Datastore; import org.mongodb.morphia.Morphia; +import org.mongodb.morphia.converters.UUIDConverter; import org.mongodb.morphia.logging.MorphiaLoggerFactory; import org.mongodb.morphia.logging.slf4j.SLF4JLoggerImplFactory; import redis.clients.jedis.JedisPool; +import spark.Spark; import java.io.FileInputStream; import java.io.InputStream; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import static spark.Spark.*; +import static spark.route.RouteOverview.enableRouteOverview; @Slf4j public final class APIv3 { @@ -120,6 +122,7 @@ public final class APIv3 { Morphia morphia = new Morphia(); morphia.mapPackage("net.frozenorb.apiv3.accessor"); + morphia.getMapper().getConverters().addConverter(new UUIDConverter()); datastore = morphia.createDatastore(mongoClient, config.getProperty("mongo.database")); datastore.ensureIndexes(); @@ -234,6 +237,8 @@ public final class APIv3 { delete("/user/:id/meta/:serverGroup", new DELETEUserMeta(), gson::toJson); delete("/user/:id/punishment", new DELETEUserPunishment(), gson::toJson); + enableRouteOverview("/routes"); + // There's no way to do a JSON 404 page w/o doing this :( get("/*", new NotFound(), gson::toJson); post("/*", new NotFound(), gson::toJson); @@ -245,6 +250,10 @@ public final class APIv3 { // A lot of unneeded .toString()'s and cloning objects is our ghetto null validation. MongoDatabase importFrom = new MongoClient(oldIp).getDatabase("minehq"); Map mongoIdToUUID = new HashMap<>(); + AtomicInteger skippedUsers = new AtomicInteger(); + AtomicInteger skippedPunishments = new AtomicInteger(); + AtomicInteger skippedGrants = new AtomicInteger(); + AtomicInteger skippedIpLogs = new AtomicInteger(); importFrom.getCollection("user").find().forEach(new Block() { @@ -256,7 +265,13 @@ public final class APIv3 { return; } - UUID uuid = UUID.fromString(uuidString.substring(0, 7) + "-" + uuidString.substring(7, 11) + "-" + uuidString.substring(11, 15) + "-" + uuidString.substring(15, 20) + "-" + uuidString.substring(20, uuidString.length())); + UUID uuid = UUID.fromString(uuidString.replaceFirst( "([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]+)", "$1-$2-$3-$4-$5" )); + + if (!UUIDUtils.isAcceptableUUID(uuid)) { + skippedUsers.incrementAndGet(); + return; + } + mongoIdToUUID.put(user.getObjectId("_id"), uuid); User created = new User( @@ -290,6 +305,7 @@ public final class APIv3 { UUID target = mongoIdToUUID.get(((DBRef) punishment.get("user")).getId()); if (target == null) { + skippedPunishments.incrementAndGet(); return; } @@ -324,6 +340,7 @@ public final class APIv3 { UUID target = mongoIdToUUID.get(((DBRef) grant.get("target")).getId()); if (target == null) { + skippedGrants.incrementAndGet(); return; } @@ -366,6 +383,7 @@ public final class APIv3 { UUID user = mongoIdToUUID.get(((DBRef) ipLogEntry.get("user")).getId()); if (user == null || ipLogEntry.getString("ip") == null) { + skippedIpLogs.incrementAndGet(); return; } @@ -398,6 +416,8 @@ public final class APIv3 { log.info("Created ip log entry " + created.getId() + " (" + created.getUser() + " - " + created.getUserIp() + ")"); } }); + + log.info("Skipped " + skippedUsers.get() + " users, " + skippedPunishments.get() + " punishments, " + skippedGrants.get() + " grants, and " + skippedIpLogs.get() + " ip logs"); } } \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java b/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java index d442d3a..3bb09f2 100644 --- a/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java +++ b/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java @@ -22,7 +22,7 @@ public final class ServerGroup { // We define these HashSets up here because, in the event they're // empty, Morphia will load them as null, not empty sets. @Getter @Setter @ExcludeFromReplies private Set announcements = new HashSet<>(); - @Getter @ExcludeFromReplies private Map> permissions = new HashMap<>(); + @Getter @Setter @ExcludeFromReplies private Map> permissions = new HashMap<>(); public static ServerGroup byId(String id) { return APIv3.getDatastore().createQuery(ServerGroup.class).field("id").equal(id).get(); diff --git a/src/main/java/net/frozenorb/apiv3/models/User.java b/src/main/java/net/frozenorb/apiv3/models/User.java index 959ac6b..e980992 100644 --- a/src/main/java/net/frozenorb/apiv3/models/User.java +++ b/src/main/java/net/frozenorb/apiv3/models/User.java @@ -25,7 +25,7 @@ public final class User { @Getter @Id private UUID id; @Getter @Indexed private String lastUsername; - @Getter @ExcludeFromReplies private Map aliases; + @Getter @ExcludeFromReplies private Map aliases = new HashMap<>(); @Getter @Setter @ExcludeFromReplies private String totpSecret; @Getter @Indexed @ExcludeFromReplies @Setter private String emailToken; @Getter @ExcludeFromReplies @Setter private Date emailTokenSetAt; @@ -106,7 +106,7 @@ public final class User { } public List getGrants() { - return APIv3.getDatastore().createQuery(Grant.class).field("target").equal(id).asList(); + return APIv3.getDatastore().createQuery(Grant.class).field("user").equal(id).asList(); } public List getIPLog() { @@ -114,7 +114,7 @@ public final class User { } public IPLogEntry getIPLogEntry(String ip) { - IPLogEntry existing = APIv3.getDatastore().createQuery(IPLogEntry.class).field("user").equal(id).field("ip").equal(ip).get(); + IPLogEntry existing = APIv3.getDatastore().createQuery(IPLogEntry.class).field("user").equal(id).field("userIp").equal(ip).get(); if (existing == null) { existing = new IPLogEntry(this, ip); @@ -125,11 +125,11 @@ public final class User { } public List getPunishments() { - return APIv3.getDatastore().createQuery(Punishment.class).field("target").equal(id).asList(); + return APIv3.getDatastore().createQuery(Punishment.class).field("user").equal(id).asList(); } public List getPunishments(Collection types) { - return APIv3.getDatastore().createQuery(Punishment.class).field("target").equal(id).field("type").in(types).asList(); + return APIv3.getDatastore().createQuery(Punishment.class).field("user").equal(id).field("type").in(types).asList(); } public UserMetaEntry getMeta(ServerGroup group) { diff --git a/src/main/java/net/frozenorb/apiv3/routes/users/GETStaff.java b/src/main/java/net/frozenorb/apiv3/routes/users/GETStaff.java index 35e0126..271eb1a 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/users/GETStaff.java +++ b/src/main/java/net/frozenorb/apiv3/routes/users/GETStaff.java @@ -21,15 +21,11 @@ public final class GETStaff implements Route { } }); - Map> result = new TreeMap<>((Comparator) (first, second) -> { + Map> result = new TreeMap<>((first, second) -> { Rank firstRank = Rank.byId(first); Rank secondRank = Rank.byId(second); - if (firstRank.getWeight() > secondRank.getWeight()) { - return -1; - } - - return 1; + return Integer.compare(firstRank.getWeight(), secondRank.getWeight()); }); APIv3.getDatastore().createQuery(Grant.class).field("rank").in(staffRanks.keySet()).forEach(grant -> { diff --git a/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java b/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java index bd70f6d..e896299 100644 --- a/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java +++ b/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java @@ -1,10 +1,14 @@ package net.frozenorb.apiv3.utils; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import lombok.experimental.UtilityClass; +import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.models.Rank; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; @UtilityClass @@ -18,14 +22,14 @@ public class PermissionUtils { return result; } - public static Map mergeUpTo(Map> unmerged, Rank upTo) { + public static Map mergeUpTo(Map> unmerged, Rank upTo) { Map result = new HashMap<>(); for (Rank rank : Rank.values()) { - Map rankPermissions = unmerged.get(rank.getId()); + Map rankPermissions = convertToMap(unmerged.get(rank.getId())); // If there's no permissions defined for this rank just skip it. - if (rankPermissions != null) { + if (!rankPermissions.isEmpty()) { result = mergePermissions(result, rankPermissions); } @@ -42,4 +46,69 @@ public class PermissionUtils { return ImmutableMap.of(); } + private static Map convertToMap(List unconvered) { + if (unconvered == null) { + return ImmutableMap.of(); + } + + Map result = new HashMap<>(); + + for (String permission : unconvered) { + boolean negate = permission.startsWith("-"); + + if (negate) { + result.put(permission.substring(1), false); + } else { + result.put(permission, true); + } + } + + return result; + } + + public static Map> fromLegacy(String defaultGroup, String customGroup) { + Map> defaultPerms = legacyServerGroupToPerms(APIv3.getGson().fromJson(defaultGroup, HashMap.class)); + Map> customPerms = legacyServerGroupToPerms(APIv3.getGson().fromJson(customGroup, HashMap.class)); + + for (Map.Entry> customPerm : customPerms.entrySet()) { + List original = defaultPerms.get(customPerm.getKey()); + + original.addAll(customPerm.getValue()); + + defaultPerms.put(customPerm.getKey(), new ArrayList<>(ImmutableSet.copyOf(original))); + } + + defaultPerms.remove("coowner"); + defaultPerms.remove("allow-vpns"); + defaultPerms.remove("head-admin"); + defaultPerms.remove("registered"); + defaultPerms.remove("mvp"); + defaultPerms.remove("super-head-admin"); + defaultPerms.remove("trial-mod"); + defaultPerms.remove("forcedeathkick"); + return defaultPerms; + } + + private static Map> legacyServerGroupToPerms(Map group) { + Map> result = new HashMap<>(); + Map permissions = (Map) group.get("permissions"); + + for (Map.Entry entry : permissions.entrySet()) { + List perms = (List) ((Map) entry.getValue()).get("grant"); + String rank = entry.getKey(); + + if (rank.equalsIgnoreCase("unban") || rank.equalsIgnoreCase("pass") || rank.equalsIgnoreCase("pink") || rank.equalsIgnoreCase("jrdev")) { + continue; + } else if (rank.equalsIgnoreCase("high_roller")) { + rank = "high-roller"; + } else if (rank.equalsIgnoreCase("dev")) { + rank = "developer"; + } + + result.put(rank, perms); + } + + return result; + } + } \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/utils/TOTPUtils.java b/src/main/java/net/frozenorb/apiv3/utils/TOTPUtils.java index 04c954f..f094c50 100644 --- a/src/main/java/net/frozenorb/apiv3/utils/TOTPUtils.java +++ b/src/main/java/net/frozenorb/apiv3/utils/TOTPUtils.java @@ -1,6 +1,7 @@ package net.frozenorb.apiv3.utils; import com.warrenstrange.googleauth.GoogleAuthenticator; +import com.warrenstrange.googleauth.GoogleAuthenticatorConfig; import com.warrenstrange.googleauth.GoogleAuthenticatorKey; import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator; import lombok.experimental.UtilityClass; @@ -13,7 +14,7 @@ import java.util.concurrent.TimeUnit; @UtilityClass public class TOTPUtils { - private static GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator(); + private static GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator(new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder().setWindowSize(10).build()); public static GoogleAuthenticatorKey generateTOTPKey() { return googleAuthenticator.createCredentials();