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();