diff --git a/src/main/java/net/frozenorb/apiv3/model/IpIntel.java b/src/main/java/net/frozenorb/apiv3/model/IpIntel.java index 4e8c943..2476c3a 100644 --- a/src/main/java/net/frozenorb/apiv3/model/IpIntel.java +++ b/src/main/java/net/frozenorb/apiv3/model/IpIntel.java @@ -4,6 +4,8 @@ import com.mongodb.async.SingleResultCallback; import com.mongodb.async.client.MongoCollection; import fr.javatic.mongo.jacksonCodec.Entity; import fr.javatic.mongo.jacksonCodec.objectId.Id; +import io.vertx.core.CompositeFuture; +import io.vertx.core.Future; import lombok.AllArgsConstructor; import lombok.Getter; import net.frozenorb.apiv3.APIv3; @@ -12,8 +14,8 @@ import net.frozenorb.apiv3.util.MaxMindUtils; import org.bson.Document; import java.time.Instant; -import java.util.LinkedList; -import java.util.List; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; @Entity @AllArgsConstructor @@ -59,6 +61,58 @@ public final class IpIntel { }); } + public static void findOrCreateByIdGrouped(Collection search, SingleResultCallback> callback) { + ipIntelCollection.find(new Document("_id", new Document("$in", search))).into(new LinkedList<>(), (existingIntel, error) -> { + if (error != null) { + callback.onResult(null, error); + return; + } + + Map result = new ConcurrentHashMap<>(); + + for (IpIntel ipIntel : existingIntel) { + result.put(ipIntel.getId(), ipIntel); + } + + List createNewIntelFutures = new ArrayList<>(); + + search.forEach((ip) -> { + if (result.containsKey(ip)) { + return; + } + + Future createNewIntelFuture = Future.future(); + createNewIntelFutures.add(createNewIntelFuture); + + MaxMindUtils.getInsights(ip, (maxMindResult, error2) -> { + if (error2 != null) { + createNewIntelFuture.fail(error2); + return; + } + + IpIntel newIpIntel = new IpIntel(ip, maxMindResult); + + ipIntelCollection.insertOne(newIpIntel, (ignored, error3) -> { + if (error3 != null) { + createNewIntelFuture.fail(error3); + } else { + result.put(ip, newIpIntel); + createNewIntelFuture.complete(); + } + }); + }); + }); + + CompositeFuture.all(createNewIntelFutures).setHandler((creationStatus) -> { + if (creationStatus.failed()) { + callback.onResult(null, creationStatus.cause()); + } else { + callback.onResult(result, null); + } + }); + }); + } + private IpIntel() {} // For Jackson private IpIntel(String ip, MaxMindResult result) { diff --git a/src/main/java/net/frozenorb/apiv3/route/servers/POSTServersHeartbeat.java b/src/main/java/net/frozenorb/apiv3/route/servers/POSTServersHeartbeat.java index acfa4b9..70c630b 100644 --- a/src/main/java/net/frozenorb/apiv3/route/servers/POSTServersHeartbeat.java +++ b/src/main/java/net/frozenorb/apiv3/route/servers/POSTServersHeartbeat.java @@ -37,11 +37,13 @@ public final class POSTServersHeartbeat implements Handler { Server actorServer = Server.findById(actor.getName()); ServerGroup actorServerGroup = ServerGroup.findById(actorServer.getServerGroup()); JsonObject requestBody = ctx.getBodyAsJson(); - Map playerNames = extractPlayerNames(requestBody.getJsonObject("players")); + JsonObject players = requestBody.getJsonObject("players"); + Map playerNames = extractPlayerNames(players); + Map playerIps = extractPlayerIps(players); CompositeFuture.all( createInfoResponse(actorServer, requestBody.getDouble("lastTps"), playerNames), - createPlayerResponse(actorServer, playerNames), + createPlayerResponse(actorServer, playerNames, playerIps), createPermissionsResponse(actorServerGroup), createEventsResponse(requestBody.getJsonArray("events")) ).setHandler((result) -> { @@ -74,19 +76,22 @@ public final class POSTServersHeartbeat implements Handler { return callback; } - private Future> createPlayerResponse(Server server, Map playerNames) { + private Future> createPlayerResponse(Server server, Map playerNames, Map playerIps) { Future> callback = Future.future(); Future> userLookupCallback = Future.future(); + Future> ipIntelCallback = Future.future(); Future>> grantLookupCallback = Future.future(); Future>> punishmentLookupCallback = Future.future(); User.findOrCreateByIdGrouped(playerNames, new MongoToVertxCallback<>(userLookupCallback)); + IpIntel.findOrCreateByIdGrouped(playerIps.values(), new MongoToVertxCallback<>(ipIntelCallback)); Grant.findByUserGrouped(playerNames.keySet(), new MongoToVertxCallback<>(grantLookupCallback)); Punishment.findByUserGrouped(playerNames.keySet(), new MongoToVertxCallback<>(punishmentLookupCallback)); CompositeFuture.all( userLookupCallback, + ipIntelCallback, grantLookupCallback, punishmentLookupCallback ).setHandler((batchLookupInfo) -> { @@ -96,13 +101,14 @@ public final class POSTServersHeartbeat implements Handler { } Map users = batchLookupInfo.result().result(0); - Map> grants = batchLookupInfo.result().result(1); - Map> punishments = batchLookupInfo.result().result(2); + Map ipIntel = batchLookupInfo.result().result(1); + Map> grants = batchLookupInfo.result().result(2); + Map> punishments = batchLookupInfo.result().result(3); Map loginInfoFutures = new HashMap<>(); users.forEach((uuid, user) -> { Future> loginInfoFuture = Future.future(); - createLoginInfo(user, server, grants.get(uuid), punishments.get(uuid), loginInfoFuture); + createLoginInfo(user, server, ipIntel.get(playerIps.get(uuid)), grants.get(uuid), punishments.get(uuid), loginInfoFuture); loginInfoFutures.put(uuid, loginInfoFuture); }); @@ -170,7 +176,22 @@ public final class POSTServersHeartbeat implements Handler { return result; } - private void createLoginInfo(User user, Server server, List grants, List punishments, Future> callback) { + private Map extractPlayerIps(JsonObject players) { + Map result = new HashMap<>(); + + players.forEach((entry) -> { + UUID uuid = UUID.fromString(entry.getKey()); + JsonObject data = (JsonObject) entry.getValue(); + + if (UuidUtils.isAcceptableUuid(uuid)) { + result.put(uuid, data.getString("userIp")); + } + }); + + return result; + } + + private void createLoginInfo(User user, Server server, IpIntel ipIntel, List grants, List punishments, Future> callback) { if (user.seenOnServer(server)) { user.save((ignored, error) -> { if (error != null) { @@ -178,11 +199,10 @@ public final class POSTServersHeartbeat implements Handler { return; } - // TODO: IP BAN INFO (AND FOR LINE BELOW) - user.getLoginInfo(server, null, punishments, ImmutableList.of(), grants, new MongoToVertxCallback<>(callback)); + user.getLoginInfo(server, ipIntel, punishments, ImmutableList.of(), grants, new MongoToVertxCallback<>(callback)); }); } else { - user.getLoginInfo(server, null, punishments, ImmutableList.of(), grants, new MongoToVertxCallback<>(callback)); + user.getLoginInfo(server, ipIntel, punishments, ImmutableList.of(), grants, new MongoToVertxCallback<>(callback)); } } diff --git a/src/main/java/net/frozenorb/apiv3/route/users/POSTUsersIdLogin.java b/src/main/java/net/frozenorb/apiv3/route/users/POSTUsersIdLogin.java index 24757c8..de44af0 100644 --- a/src/main/java/net/frozenorb/apiv3/route/users/POSTUsersIdLogin.java +++ b/src/main/java/net/frozenorb/apiv3/route/users/POSTUsersIdLogin.java @@ -31,7 +31,7 @@ public final class POSTUsersIdLogin implements Handler { BlockingCallback userCallback = new BlockingCallback<>(); User.findById(uuid, userCallback); User user = userCallback.get(); - String username = requestBody.getString("username"); + String currentUsername = requestBody.getString("username"); String userIp = requestBody.getString("userIp"); Actor actor = ctx.get("actor"); @@ -48,12 +48,12 @@ public final class POSTUsersIdLogin implements Handler { } if (user == null) { - user = new User(uuid, username); + user = new User(uuid, currentUsername); BlockingCallback nameCollisionCallback = new BlockingCallback<>(); user.checkNameCollisions(nameCollisionCallback); nameCollisionCallback.get(); BlockingCallback insertCallback = new BlockingCallback<>(); - user.checkNameCollisions(insertCallback); + user.insert(insertCallback); insertCallback.get(); } @@ -76,14 +76,15 @@ public final class POSTUsersIdLogin implements Handler { callback.get(); } - if (!username.equals(user.getLastUsername())) { + String lastUsername = user.getLastUsername(); + user.updateUsername(currentUsername); + + if (!currentUsername.equals(lastUsername)) { BlockingCallback callback = new BlockingCallback<>(); user.checkNameCollisions(callback); callback.get(); } - user.updateUsername(username); - user.getLoginInfo(actorServer, userIp, (loginInfo, error) -> { if (error != null) { ErrorUtils.respondInternalError(ctx, error);