ayy last fake commit

This commit is contained in:
Colin McDonald 2016-06-16 00:08:44 -04:00
parent 7e462e1915
commit 7018a3c5e2
77 changed files with 1289 additions and 810 deletions

View File

@ -7,8 +7,6 @@ mongo.password=
redis.address=localhost redis.address=localhost
redis.port=6379 redis.port=6379
http.port=80 http.port=80
twillio.accountSID=AC9e2f88c5690134d29a56f698de3cd740
twillio.authToken=982592505a171d3be6b0722f5ecacc0e
mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ
bugsnag.apiKey=0e47fba8b825416b7cbc839066184509 bugsnag.apiKey=0e47fba8b825416b7cbc839066184509
auth.websiteApiKey=RVbp4hY6sCFVaf auth.websiteApiKey=RVbp4hY6sCFVaf

19
pom.xml
View File

@ -19,6 +19,7 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId> <artifactId>maven-jar-plugin</artifactId>
<version>3.0.1</version>
<configuration> <configuration>
<archive> <archive>
<manifest> <manifest>
@ -33,9 +34,9 @@
<version>2.3</version> <version>2.3</version>
<configuration> <configuration>
<artifactSet> <artifactSet>
<includes> <excludes>
<include>*:*</include> <exclude>org.projectlombok:lombok</exclude>
</includes> </excludes>
</artifactSet> </artifactSet>
</configuration> </configuration>
<executions> <executions>
@ -108,18 +109,6 @@
<version>2.6.0</version> <version>2.6.0</version>
</dependency> </dependency>
<!-- Notifications -->
<dependency>
<groupId>com.cribbstechnologies.clients</groupId>
<artifactId>mandrillClient</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>com.twilio.sdk</groupId>
<artifactId>twilio-java-sdk</artifactId>
<version>6.3.0</version>
</dependency>
<!-- TOTP --> <!-- TOTP -->
<dependency> <dependency>
<groupId>com.warrenstrange</groupId> <groupId>com.warrenstrange</groupId>

View File

@ -7,56 +7,49 @@ import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.net.MediaType;
import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.mongodb.Block;
import com.mongodb.ConnectionString; import com.mongodb.ConnectionString;
import com.mongodb.MongoCredential; import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.async.client.MongoClient;
import com.mongodb.async.client.MongoClientSettings; import com.mongodb.async.client.MongoClientSettings;
import com.mongodb.async.client.MongoClients; import com.mongodb.async.client.MongoClients;
import com.mongodb.async.client.MongoDatabase; import com.mongodb.async.client.MongoDatabase;
import com.mongodb.connection.ClusterConnectionMode; import com.mongodb.client.model.IndexModel;
import com.mongodb.connection.ClusterSettings; import com.mongodb.connection.ClusterSettings;
import com.timgroup.statsd.NonBlockingStatsDClient;
import com.timgroup.statsd.StatsDClient;
import fr.javatic.mongo.jacksonCodec.JacksonCodecProvider; import fr.javatic.mongo.jacksonCodec.JacksonCodecProvider;
import fr.javatic.mongo.jacksonCodec.ObjectMapperFactory; import fr.javatic.mongo.jacksonCodec.ObjectMapperFactory;
import io.vertx.core.AbstractVerticle; import io.vertx.core.AbstractVerticle;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClient; import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServer;
import io.vertx.ext.web.Router; import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler; import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.LoggerFormat;
import io.vertx.ext.web.handler.LoggerHandler; import io.vertx.ext.web.handler.LoggerHandler;
import io.vertx.ext.web.impl.BlockingHandlerDecorator;
import io.vertx.ext.web.impl.RouteImpl;
import io.vertx.redis.RedisClient; import io.vertx.redis.RedisClient;
import io.vertx.redis.RedisOptions; import io.vertx.redis.RedisOptions;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.handlers.ActorAttributeHandler; import net.frozenorb.apiv3.handlers.ActorAttributeHandler;
import net.frozenorb.apiv3.handlers.AuthorizationHandler; import net.frozenorb.apiv3.handlers.AuthorizationHandler;
import net.frozenorb.apiv3.handlers.MetricsHandler;
import net.frozenorb.apiv3.models.*;
import net.frozenorb.apiv3.routes.GETDump; import net.frozenorb.apiv3.routes.GETDump;
import net.frozenorb.apiv3.routes.GETWhoAmI; import net.frozenorb.apiv3.routes.GETWhoAmI;
import net.frozenorb.apiv3.routes.POSTMetrics; import net.frozenorb.apiv3.routes.POSTMetrics;
import net.frozenorb.apiv3.routes.announcements.GETAnnouncements; import net.frozenorb.apiv3.routes.announcements.GETAnnouncements;
import net.frozenorb.apiv3.routes.announcements.PUTAnnouncements;
import net.frozenorb.apiv3.routes.auditLog.GETAuditLog; import net.frozenorb.apiv3.routes.auditLog.GETAuditLog;
import net.frozenorb.apiv3.routes.auditLog.POSTUserAuditLogEntry;
import net.frozenorb.apiv3.routes.chatFilterList.GETChatFilterList; import net.frozenorb.apiv3.routes.chatFilterList.GETChatFilterList;
import net.frozenorb.apiv3.routes.grants.*; import net.frozenorb.apiv3.routes.grants.*;
import net.frozenorb.apiv3.routes.ipLog.GETUserIPLog; import net.frozenorb.apiv3.routes.ipBans.*;
import net.frozenorb.apiv3.routes.notificationTemplate.DELETENotificationTemplate; import net.frozenorb.apiv3.routes.ipLog.GETUserIpLog;
import net.frozenorb.apiv3.routes.notificationTemplate.GETNotificationTemplate; import net.frozenorb.apiv3.routes.notificationTemplates.DELETENotificationTemplate;
import net.frozenorb.apiv3.routes.notificationTemplate.GETNotificationTemplates; import net.frozenorb.apiv3.routes.notificationTemplates.GETNotificationTemplate;
import net.frozenorb.apiv3.routes.notificationTemplate.POSTNotificationTemplate; import net.frozenorb.apiv3.routes.notificationTemplates.GETNotificationTemplates;
import net.frozenorb.apiv3.routes.notificationTemplates.POSTNotificationTemplate;
import net.frozenorb.apiv3.routes.punishments.*; import net.frozenorb.apiv3.routes.punishments.*;
import net.frozenorb.apiv3.routes.ranks.DELETERank; import net.frozenorb.apiv3.routes.ranks.DELETERank;
import net.frozenorb.apiv3.routes.ranks.GETRank; import net.frozenorb.apiv3.routes.ranks.GETRank;
@ -68,26 +61,23 @@ import net.frozenorb.apiv3.routes.serverGroups.GETServerGroups;
import net.frozenorb.apiv3.routes.serverGroups.POSTServerGroup; import net.frozenorb.apiv3.routes.serverGroups.POSTServerGroup;
import net.frozenorb.apiv3.routes.servers.*; import net.frozenorb.apiv3.routes.servers.*;
import net.frozenorb.apiv3.routes.users.*; import net.frozenorb.apiv3.routes.users.*;
import net.frozenorb.apiv3.serialization.*; import net.frozenorb.apiv3.serialization.gson.DateTypeAdapter;
import net.frozenorb.apiv3.unsorted.BlockingCallback; import net.frozenorb.apiv3.serialization.gson.FollowAnnotationExclusionStrategy;
import net.frozenorb.apiv3.serialization.jackson.UUIDJsonDeserializer;
import net.frozenorb.apiv3.serialization.jackson.UUIDJsonSerializer;
import net.frozenorb.apiv3.serialization.mongodb.UUIDCodecProvider;
import net.frozenorb.apiv3.unsorted.BugsnagSLF4JLogger; import net.frozenorb.apiv3.unsorted.BugsnagSLF4JLogger;
import net.frozenorb.apiv3.utils.IPUtils;
import net.frozenorb.apiv3.utils.SyncUtils;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.Document; import org.bson.Document;
import org.bson.codecs.BsonValueCodecProvider; import org.bson.codecs.BsonValueCodecProvider;
import org.bson.codecs.DocumentCodecProvider; import org.bson.codecs.DocumentCodecProvider;
import org.bson.codecs.ValueCodecProvider; import org.bson.codecs.ValueCodecProvider;
import org.bson.codecs.configuration.CodecProvider; import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistries; import org.bson.codecs.configuration.CodecRegistries;
import org.bson.types.ObjectId;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j @Slf4j
public final class APIv3 extends AbstractVerticle { public final class APIv3 extends AbstractVerticle {
@ -96,38 +86,39 @@ public final class APIv3 extends AbstractVerticle {
@Getter private static MongoDatabase database; @Getter private static MongoDatabase database;
@Getter private static Properties config = new Properties(); @Getter private static Properties config = new Properties();
@Getter private static RedisClient redisClient; @Getter private static RedisClient redisClient;
@Getter private static StatsDClient statsD; private static final Gson gson = new GsonBuilder()
@Getter private static Vertx vertxInstance;
@Getter private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Date.class, new DateTypeAdapter()) .registerTypeAdapter(Date.class, new DateTypeAdapter())
.setExclusionStrategies(new FollowAnnotationExclusionStrategy()) .setExclusionStrategies(new FollowAnnotationExclusionStrategy())
.create(); .create();
@Override @Override
public void start() { public void start() {
vertxInstance = vertx;
setupConfig(); setupConfig();
setupDatabase(); setupDatabase();
setupRedis(); setupRedis();
setupMetrics();
setupBugsnag(); setupBugsnag();
//setupHttpServer(); setupHttpServer();
setupHttpClient(); setupHttpClient();
convertData("mongodb://158.69.126.126", true); /*V2Converter converter = new V2Converter("mongodb://158.69.126.126", "minehq");
converter.startConversion((ignored, error) -> {
if (error != null) {
error.printStackTrace();
}
});*/
} }
private void setupConfig() { private void setupConfig() {
try (InputStream in = new FileInputStream("apiv3.properties")) { try (InputStream in = new FileInputStream("apiv3.properties")) {
config.load(in); config.load(in);
} catch (Exception ex) { } catch (IOException ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }
} }
private void setupDatabase() { private void setupDatabase() {
ImmutableList<MongoCredential> credentials = ImmutableList.of(); List<MongoCredential> credentials = ImmutableList.of();
if (!config.getProperty("mongo.username").isEmpty()) { if (!config.getProperty("mongo.username").isEmpty()) {
credentials = ImmutableList.of( credentials = ImmutableList.of(
@ -146,8 +137,8 @@ public final class APIv3 extends AbstractVerticle {
List<CodecProvider> providers = new ArrayList<>(); List<CodecProvider> providers = new ArrayList<>();
// Our override codec // Our fixed uuid codec
providers.add(new MineHQCodecProvider()); providers.add(new UUIDCodecProvider());
// Normal providers // Normal providers
providers.add(new ValueCodecProvider()); providers.add(new ValueCodecProvider());
@ -173,9 +164,34 @@ public final class APIv3 extends AbstractVerticle {
.clusterSettings(clusterSettings) .clusterSettings(clusterSettings)
.build(); .build();
MongoClient client = MongoClients.create(settings); database = MongoClients.create(settings).getDatabase(config.getProperty("mongo.database"));
database = client.getDatabase(config.getProperty("mongo.database")); database.getCollection("auditLog").createIndexes(ImmutableList.of(
// TODO: Indexes new IndexModel(new Document("user", 1)),
new IndexModel(new Document("performedAt", 1)),
new IndexModel(new Document("type", 1))
), (a, b) -> {});
database.getCollection("grants").createIndexes(ImmutableList.of(
new IndexModel(new Document("user", 1)),
new IndexModel(new Document("rank", 1)),
new IndexModel(new Document("addedAt", 1))
), (a, b) -> {});
database.getCollection("ipLog").createIndexes(ImmutableList.of(
new IndexModel(new Document("user", 1)),
new IndexModel(new Document("user", 1).append("userIp", 1))
), (a, b) -> {});
database.getCollection("punishments").createIndexes(ImmutableList.of(
new IndexModel(new Document("user", 1)),
new IndexModel(new Document("type", 1)),
new IndexModel(new Document("addedAt", 1)),
new IndexModel(new Document("addedBy", 1))
), (a, b) -> {});
database.getCollection("users").createIndexes(ImmutableList.of(
new IndexModel(new Document("lastUsername", 1)),
new IndexModel(new Document("emailToken", 1))
), (a, b) -> {});
database.getCollection("userMeta").createIndexes(ImmutableList.of(
new IndexModel(new Document("user", 1).append("serverGroup", 1))
), (a, b) -> {});
} }
private void setupRedis() { private void setupRedis() {
@ -187,20 +203,6 @@ public final class APIv3 extends AbstractVerticle {
); );
} }
private void setupMetrics() {
statsD = new NonBlockingStatsDClient(null, "localhost", 8125);
new Timer("Metrics Task").scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
statsD.recordGaugeValue("apiv3.memory.usage", Runtime.getRuntime().totalMemory() / (1024 * 1024));
statsD.recordGaugeValue("apiv3.memory.max", Runtime.getRuntime().maxMemory() / (1024 * 1024));
}
}, TimeUnit.SECONDS.toMillis(5), TimeUnit.SECONDS.toMillis(5));
}
private void setupBugsnag() { private void setupBugsnag() {
Client bugsnag = new Client(config.getProperty("bugsnag.apiKey")); Client bugsnag = new Client(config.getProperty("bugsnag.apiKey"));
bugsnag.setReleaseStage(config.getProperty("general.releaseStage")); bugsnag.setReleaseStage(config.getProperty("general.releaseStage"));
@ -208,29 +210,40 @@ public final class APIv3 extends AbstractVerticle {
bugsnag.setLogger(new BugsnagSLF4JLogger()); bugsnag.setLogger(new BugsnagSLF4JLogger());
} }
// TODO: blockingHandler -> handler
private void setupHttpServer() { private void setupHttpServer() {
HttpServer webServer = vertx.createHttpServer(); HttpServer webServer = vertx.createHttpServer();
Router mainRouter = Router.router(vertx); Router mainRouter = Router.router(vertx);
mainRouter.route().handler(new MetricsHandler());
mainRouter.route().handler(new ActorAttributeHandler()); mainRouter.route().handler(new ActorAttributeHandler());
mainRouter.route().handler(new AuthorizationHandler()); mainRouter.route().handler(new AuthorizationHandler());
mainRouter.route().handler(LoggerHandler.create()); mainRouter.route().handler(LoggerHandler.create(LoggerFormat.TINY));
mainRouter.route().handler(BodyHandler.create()); mainRouter.route().method(HttpMethod.PUT).method(HttpMethod.POST).handler(BodyHandler.create());
// TODO: The commented out routes // TODO: The commented out routes
mainRouter.post("/metrics").blockingHandler(new POSTMetrics()); mainRouter.get("/announcements/:id").blockingHandler(new GETAnnouncements());
mainRouter.get("/announcements").blockingHandler(new GETAnnouncements()); mainRouter.put("/announcements/:id").blockingHandler(new PUTAnnouncements());
mainRouter.get("/auditLog").blockingHandler(new GETAuditLog()); mainRouter.get("/auditLog").blockingHandler(new GETAuditLog());
mainRouter.get("/chatFilterList").blockingHandler(new GETChatFilterList()); mainRouter.post("/user/:id/auditLogEntry").blockingHandler(new POSTUserAuditLogEntry());
mainRouter.get("/dump/:type").blockingHandler(new GETDump());
mainRouter.get("/whoami").blockingHandler(new GETWhoAmI()); mainRouter.get("/chatFilterList").handler(new GETChatFilterList());
mainRouter.get("/grant/:id").blockingHandler(new GETGrant()); mainRouter.get("/grant/:id").blockingHandler(new GETGrant());
mainRouter.get("/grants").blockingHandler(new GETGrants()); mainRouter.get("/grants").blockingHandler(new GETGrants());
mainRouter.get("/user/:id/grants").blockingHandler(new GETUserGrants());
mainRouter.post("/user/:id/grant").blockingHandler(new POSTUserGrant());
mainRouter.delete("/grant/:id").blockingHandler(new DELETEGrant()); mainRouter.delete("/grant/:id").blockingHandler(new DELETEGrant());
mainRouter.get("/ipBan/:id").blockingHandler(new GETIpBan());
mainRouter.get("/ipBans").blockingHandler(new GETIpBans());
mainRouter.get("/ip/:id/ipBans").blockingHandler(new GETIpIpBans());
mainRouter.post("/ip/:id/ipBan").blockingHandler(new POSTIpIpBan());
mainRouter.delete("/ipBan/:id").blockingHandler(new DELETEIpBan());
mainRouter.get("/user/:id/ipLog").blockingHandler(new GETUserIpLog());
mainRouter.get("/notificationTemplate/:id").blockingHandler(new GETNotificationTemplate()); mainRouter.get("/notificationTemplate/:id").blockingHandler(new GETNotificationTemplate());
mainRouter.get("/notificationTemplates").blockingHandler(new GETNotificationTemplates()); mainRouter.get("/notificationTemplates").blockingHandler(new GETNotificationTemplates());
mainRouter.post("/notificationTemplate").blockingHandler(new POSTNotificationTemplate()); mainRouter.post("/notificationTemplate").blockingHandler(new POSTNotificationTemplate());
@ -239,67 +252,49 @@ public final class APIv3 extends AbstractVerticle {
mainRouter.get("/punishment/:id").blockingHandler(new GETPunishment()); mainRouter.get("/punishment/:id").blockingHandler(new GETPunishment());
mainRouter.get("/punishments").blockingHandler(new GETPunishments()); mainRouter.get("/punishments").blockingHandler(new GETPunishments());
mainRouter.get("/user/:id/punishments").blockingHandler(new GETUserPunishments());
mainRouter.post("/user/:id/punish").blockingHandler(new POSTUserPunish());
mainRouter.delete("/punishment/:id").blockingHandler(new DELETEPunishment()); mainRouter.delete("/punishment/:id").blockingHandler(new DELETEPunishment());
mainRouter.delete("/user/:id/punishment").blockingHandler(new DELETEUserPunishment());
mainRouter.get("/rank/:id").blockingHandler(new GETRank()); mainRouter.get("/rank/:id").handler(new GETRank());
mainRouter.get("/ranks").blockingHandler(new GETRanks()); mainRouter.get("/ranks").handler(new GETRanks());
mainRouter.post("/rank").blockingHandler(new POSTRank()); mainRouter.post("/rank").blockingHandler(new POSTRank());
//put("/rank/:id").blockingHandler(new PUTRank()); //mainRouter.put("/rank/:id").blockingHandler(new PUTRank());
mainRouter.delete("/rank/:id").blockingHandler(new DELETERank()); mainRouter.delete("/rank/:id").blockingHandler(new DELETERank());
mainRouter.get("/serverGroup/:id").blockingHandler(new GETServerGroup()); mainRouter.get("/serverGroup/:id").handler(new GETServerGroup());
mainRouter.get("/serverGroups").blockingHandler(new GETServerGroups()); mainRouter.get("/serverGroups").handler(new GETServerGroups());
mainRouter.post("/serverGroup").blockingHandler(new POSTServerGroup()); mainRouter.post("/serverGroup").blockingHandler(new POSTServerGroup());
//put("/serverGroup/:id").blockingHandler(new PUTServerGroup()); //mainRouter.put("/serverGroup/:id").blockingHandler(new PUTServerGroup());
mainRouter.delete("/serverGroup/:id").blockingHandler(new DELETEServerGroup()); mainRouter.delete("/serverGroup/:id").blockingHandler(new DELETEServerGroup());
mainRouter.get("/server/:id").blockingHandler(new GETServer()); mainRouter.get("/server/:id").handler(new GETServer());
mainRouter.get("/servers").blockingHandler(new GETServers()); mainRouter.get("/servers").handler(new GETServers());
mainRouter.post("/server/heartbeat").handler(new POSTServerHeartbeat()); mainRouter.post("/server/heartbeat").handler(new POSTServerHeartbeat());
mainRouter.post("/server").blockingHandler(new POSTServer()); mainRouter.post("/server").blockingHandler(new POSTServer());
//put("/server/:id").blockingHandler(new PUTServer()); //mainRouter.put("/server/:id").blockingHandler(new PUTServer());
mainRouter.delete("/server/:id").blockingHandler(new DELETEServer()); mainRouter.delete("/server/:id").blockingHandler(new DELETEServer());
mainRouter.get("/staff").blockingHandler(new GETStaff()); mainRouter.get("/staff").blockingHandler(new GETStaff());
mainRouter.get("/user/:id").blockingHandler(new GETUser());
mainRouter.get("/user/:id/details").blockingHandler(new GETUserDetails()); mainRouter.get("/user/:id/details").blockingHandler(new GETUserDetails());
mainRouter.get("/user/:id/meta/:serverGroup").blockingHandler(new GETUserMeta()); mainRouter.get("/user/:id/meta/:serverGroup").blockingHandler(new GETUserMeta());
mainRouter.get("/user/:id/grants").blockingHandler(new GETUserGrants());
mainRouter.get("/user/:id/punishments").blockingHandler(new GETUserPunishments());
mainRouter.get("/user/:id/ipLog").blockingHandler(new GETUserIPLog());
mainRouter.get("/user/:id/requiresTOTP").blockingHandler(new GETUserRequiresTOTP()); mainRouter.get("/user/:id/requiresTOTP").blockingHandler(new GETUserRequiresTOTP());
mainRouter.get("/user/:id/verifyPassword").blockingHandler(new GETUserVerifyPassword()); mainRouter.get("/user/:id/verifyPassword").blockingHandler(new GETUserVerifyPassword());
mainRouter.get("/user/:id").blockingHandler(new GETUser()); mainRouter.post("/user/confirmRegister/:emailToken").blockingHandler(new POSTUserConfirmRegister());
mainRouter.post("/user/:id/verifyTOTP").blockingHandler(new POSTUserVerifyTOTP()); mainRouter.post("/user/:id/leave").handler(new POSTUserLeave());
mainRouter.post("/user/:id/grant").blockingHandler(new POSTUserGrant()); mainRouter.post("/user/:id/login").handler(new POSTUserLogin());
mainRouter.post("/user/:id/punish").blockingHandler(new POSTUserPunish());
mainRouter.post("/user/:id/login").blockingHandler(new POSTUserLogin());
mainRouter.post("/user/:id/leave").blockingHandler(new POSTUserLeave());
mainRouter.post("/user/:id/notify").blockingHandler(new POSTUserNotify()); mainRouter.post("/user/:id/notify").blockingHandler(new POSTUserNotify());
mainRouter.post("/user/:id/register").blockingHandler(new POSTUserRegister()); mainRouter.post("/user/:id/register").blockingHandler(new POSTUserRegister());
mainRouter.post("/user/:id/setupTOTP").blockingHandler(new POSTUserSetupTOTP()); mainRouter.post("/user/:id/setupTOTP").blockingHandler(new POSTUserSetupTOTP());
mainRouter.post("/user/confirmRegister/:emailToken").blockingHandler(new POSTUserConfirmRegister()); mainRouter.post("/user/:id/verifyTOTP").blockingHandler(new POSTUserVerifyTOTP());
mainRouter.put("/user/:id/meta/:serverGroup").blockingHandler(new PUTUserMeta()); mainRouter.put("/user/:id/meta/:serverGroup").blockingHandler(new PUTUserMeta());
mainRouter.delete("/user/:id/meta/:serverGroup").blockingHandler(new DELETEUserMeta()); mainRouter.delete("/user/:id/meta/:serverGroup").blockingHandler(new DELETEUserMeta());
mainRouter.delete("/user/:id/punishment").blockingHandler(new DELETEUserPunishment());
mainRouter.getRoutes().forEach((route) -> { mainRouter.get("/dump/:type").handler(new GETDump());
try { mainRouter.get("/whoami").handler(new GETWhoAmI());
RouteImpl impl = (RouteImpl) route; mainRouter.post("/metrics").blockingHandler(new POSTMetrics());
Field field = RouteImpl.class.getDeclaredField("contextHandler");
field.setAccessible(true);
Handler<RoutingContext> handler = (Handler<RoutingContext>) field.get(impl);
if (handler instanceof BlockingHandlerDecorator) {
field = BlockingHandlerDecorator.class.getDeclaredField("decoratedHandler");
field.setAccessible(true);
handler = (Handler<RoutingContext>) field.get(handler);
}
System.out.println(route.getPath() + " is handled by " + handler.getClass());
} catch (Exception ex) {
}
});
int port = Integer.parseInt(config.getProperty("http.port")); int port = Integer.parseInt(config.getProperty("http.port"));
webServer.requestHandler(mainRouter::accept).listen(port); webServer.requestHandler(mainRouter::accept).listen(port);
@ -314,172 +309,9 @@ public final class APIv3 extends AbstractVerticle {
} }
public static void respondJson(RoutingContext ctx, int code, Object response) { public static void respondJson(RoutingContext ctx, int code, Object response) {
ctx.response().putHeader("Content-Type", "application/json"); ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString());
ctx.response().setStatusCode(code); ctx.response().setStatusCode(code);
ctx.response().end(gson.toJson(response)); ctx.response().end(gson.toJson(response));
} }
private void convertData(String oldIp, boolean forReal) {
// A lot of unneeded .toString()'s and cloning objects is our ghetto null validation.
MongoDatabase importFrom = MongoClients.create(oldIp).getDatabase("minehq");
Map<ObjectId, UUID> mongoIdToUUID = new HashMap<>();
AtomicInteger skippedUsers = new AtomicInteger();
AtomicInteger skippedPunishments = new AtomicInteger();
AtomicInteger skippedGrants = new AtomicInteger();
AtomicInteger skippedIpLogs = new AtomicInteger();
SyncUtils.blockMulti(importFrom.getCollection("user").find()).forEach((user) -> {
String uuidString = String.valueOf(user.get("uuid"));
if (uuidString == null || uuidString.length() != 32) {
return;
}
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(
uuid,
String.valueOf(user.get("name")).toString(),
ImmutableMap.of(user.getString("name"), user.getDate("joined")),
null,
null,
null,
null,
user.getString("email"),
user.getString("phone"),
"INVALID",
user.getDate("joined"),
user.getDate("joined"),
false
);
if (forReal) {
created.insert();
}
log.info("Created user " + created.getLastUsername() + " (" + created.getId() + ")");
});
SyncUtils.blockMulti(importFrom.getCollection("punishment").find()).forEach((punishment) -> {
UUID target = mongoIdToUUID.get(((Map<String, Object>) punishment.get("user")).get("$id"));
if (target == null) {
skippedPunishments.incrementAndGet();
return;
}
// Old punishments have this value set to false to indicate they're not active anymore.
if (punishment.containsKey("active") && !punishment.getBoolean("active")) {
return;
}
Punishment created = new Punishment(
new ObjectId().toString(),
target,
punishment.getString("reason").toString(),
Punishment.PunishmentType.valueOf(punishment.getString("type").toUpperCase()),
punishment.getDate("expires"),
punishment.containsKey("meta") ? (punishment.get("meta") instanceof List ? ImmutableMap.of() : (Document) punishment.get("meta")) : ImmutableMap.of(),
punishment.containsKey("addedBy") ? mongoIdToUUID.get(((Map<String, Object>) punishment.get("addedBy")).get("$id")) : null,
(Date) punishment.getDate("created").clone(),
punishment.containsKey("createdOn") ? String.valueOf(((Map<String, Object>) punishment.get("createdOn")).get("$id")) : "Website",
punishment.containsKey("createdOn") ? ActorType.SERVER : ActorType.WEBSITE,
punishment.containsKey("removedBy") ? (((Map<String, Object>) punishment.get("removedBy")).get("$ref").equals("user") ? mongoIdToUUID.get(((Map<String, Object>) punishment.get("removedBy")).get("$id")) : null) : null,
punishment.getDate("created"),
punishment.containsKey("removalReason") ? punishment.getString("removalReason").toString() : ""
);
if (forReal) {
created.insert();
}
log.info("Created punishment " + created.getId() + " (" + created.getType() + ")");
});
SyncUtils.blockMulti(importFrom.getCollection("grant").find()).forEach((grant) -> {
UUID target = mongoIdToUUID.get(((Map<String, Object>) grant.get("target")).get("$id"));
if (target == null) {
skippedGrants.incrementAndGet();
return;
}
String rank = grant.getString("role");
if (rank.equalsIgnoreCase("unban") || rank.equalsIgnoreCase("pass") || rank.equalsIgnoreCase("pink") || rank.equalsIgnoreCase("jrdev")) {
return;
} else if (rank.equalsIgnoreCase("high_roller")) {
rank = "high-roller";
} else if (rank.equalsIgnoreCase("dev")) {
rank = "developer";
}
Grant created = new Grant(
new ObjectId().toString(),
target,
grant.containsKey("comment") ? grant.getString("comment") : "",
grant.containsKey("scope") ? ImmutableSet.copyOf((Collection<String>) grant.get("scope")) : ImmutableSet.of(),
rank,
grant.getDate("expires"),
grant.containsKey("addedBy") ? mongoIdToUUID.get(((Map<String, Object>) grant.get("addedBy")).get("$id")) : null,
grant.containsKey("created") ? grant.getDate("created") : new Date(),
null,
null,
null
);
if (forReal) {
created.insert();
}
log.info("Created grant " + created.getId() + " (" + created.getRank() + ")");
});
SyncUtils.blockMulti(importFrom.getCollection("iplog").find()).forEach((ipLogEntry) -> {
UUID user = mongoIdToUUID.get(((Map<String, Object>) ipLogEntry.get("user")).get("$id"));
if (user == null || ipLogEntry.getString("ip") == null) {
skippedIpLogs.incrementAndGet();
return;
}
String ip = ipLogEntry.getString("ip").replace("/", "");
if (!IPUtils.isValidIP(ip)) {
return;
}
Date lastSeen = ipLogEntry.getDate("lastSeen");
if (lastSeen == null) {
lastSeen = new Date();
}
IPLogEntry created = new IPLogEntry(
new ObjectId().toString(),
user,
ip,
lastSeen,
lastSeen,
((Number) ipLogEntry.get("uses")).intValue()
);
if (forReal) {
created.insert();
}
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");
}
} }

View File

@ -6,7 +6,6 @@ final class Main {
public static void main(String[] args) { public static void main(String[] args) {
System.setProperty("vertx.logger-delegate-factory-class-name", "io.vertx.core.logging.SLF4JLogDelegateFactory"); System.setProperty("vertx.logger-delegate-factory-class-name", "io.vertx.core.logging.SLF4JLogDelegateFactory");
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "info");
Vertx.vertx().deployVerticle(new APIv3()); Vertx.vertx().deployVerticle(new APIv3());
} }

View File

@ -1,6 +1,5 @@
package net.frozenorb.apiv3.actors; package net.frozenorb.apiv3.actors;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import net.frozenorb.apiv3.models.Server; import net.frozenorb.apiv3.models.Server;

View File

@ -1,13 +1,9 @@
package net.frozenorb.apiv3.actors; package net.frozenorb.apiv3.actors;
import com.google.common.collect.ImmutableSet;
import lombok.Getter; import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Permissions; import net.frozenorb.apiv3.unsorted.Permissions;
import java.util.Set;
public final class UserActor implements Actor { public final class UserActor implements Actor {
@Getter private final User user; @Getter private final User user;

View File

@ -1,8 +1,8 @@
package net.frozenorb.apiv3.auditLog; package net.frozenorb.apiv3.auditLog;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor; import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.models.AuditLogEntry; import net.frozenorb.apiv3.models.AuditLogEntry;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
@ -12,14 +12,19 @@ import java.util.Map;
@UtilityClass @UtilityClass
public class AuditLog { public class AuditLog {
public static void log(User performedBy, String performedByIp, Actor actor, AuditLogActionType actionType) { public static void log(User performedBy, String performedByIp, Actor actor, AuditLogActionType actionType, SingleResultCallback<AuditLogEntry> callback) {
log(performedBy, performedByIp, actor, actionType, ImmutableMap.of()); log(performedBy, performedByIp, actor, actionType, ImmutableMap.of(), callback);
} }
public static void log(User performedBy, String performedByIp, Actor actor, AuditLogActionType actionType, Map<String, Object> actionData) { public static void log(User performedBy, String performedByIp, Actor actor, AuditLogActionType actionType, Map<String, Object> actionData, SingleResultCallback<AuditLogEntry> callback) {
AuditLogEntry entry = new AuditLogEntry(performedBy, performedByIp, actor, actionType, actionData); AuditLogEntry entry = new AuditLogEntry(performedBy, performedByIp, actor, actionType, actionData);
entry.insert(); entry.insert((ignored, error) -> {
APIv3.getStatsD().incrementCounter("apiv3.auditLog.insertions"); if (error != null) {
callback.onResult(null, error);
} else {
callback.onResult(entry, null);
}
});
} }
} }

View File

@ -1,5 +1,6 @@
package net.frozenorb.apiv3.auditLog; package net.frozenorb.apiv3.auditLog;
import com.mongodb.async.SingleResultCallback;
import net.frozenorb.apiv3.models.AuditLogEntry; import net.frozenorb.apiv3.models.AuditLogEntry;
public enum AuditLogActionType { public enum AuditLogActionType {
@ -7,20 +8,22 @@ public enum AuditLogActionType {
DELETE_PUNISHMENT { DELETE_PUNISHMENT {
@Override @Override
public void revert(AuditLogEntry entry) { public void revert(AuditLogEntry entry, SingleResultCallback<Boolean> callback) {
callback.onResult(false, null);
} }
}, },
DELETE_GRANT { DELETE_GRANT {
@Override @Override
public void revert(AuditLogEntry entry) { public void revert(AuditLogEntry entry, SingleResultCallback<Boolean> callback) {
callback.onResult(false, null);
} }
}; };
public void revert(AuditLogEntry entry) {} public void revert(AuditLogEntry entry, SingleResultCallback<Boolean> callback) {
callback.onResult(null, new UnsupportedOperationException());
}
} }

View File

@ -0,0 +1,60 @@
package net.frozenorb.apiv3.conversion;
import com.google.common.collect.ImmutableSet;
import com.mongodb.Block;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.models.Grant;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.Collection;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
@Slf4j
public final class GrantConverter implements Block<Document> {
private Map<ObjectId, UUID> mongoIdToUUID;
public GrantConverter(Map<ObjectId, UUID> mongoIdToUUID) {
this.mongoIdToUUID = mongoIdToUUID;
}
@Override
public void apply(Document grant) {
UUID target = mongoIdToUUID.get(((Map<String, Object>) grant.get("target")).get("$id"));
if (target == null) {
return;
}
String rank = grant.getString("role");
if (rank.equalsIgnoreCase("unban") || rank.equalsIgnoreCase("pass") || rank.equalsIgnoreCase("pink") || rank.equalsIgnoreCase("jrdev")) {
return;
} else if (rank.equalsIgnoreCase("high_roller")) {
rank = "high-roller";
} else if (rank.equalsIgnoreCase("dev")) {
rank = "developer";
}
Grant created = new Grant(
new ObjectId().toString(),
target,
grant.containsKey("comment") ? grant.getString("comment") : "",
grant.containsKey("scope") ? ImmutableSet.copyOf((Collection<String>) grant.get("scope")) : ImmutableSet.of(),
rank,
grant.getDate("expires"),
grant.containsKey("addedBy") ? mongoIdToUUID.get(((Map<String, Object>) grant.get("addedBy")).get("$id")) : null,
grant.containsKey("created") ? grant.getDate("created") : new Date(),
null,
null,
null
);
created.insert();
log.info("Created grant " + created.getId() + " (" + created.getRank() + ")");
}
}

View File

@ -0,0 +1,57 @@
package net.frozenorb.apiv3.conversion;
import com.mongodb.Block;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.models.IpLogEntry;
import net.frozenorb.apiv3.utils.IpUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
@Slf4j
public final class IpLogConverter implements Block<Document> {
private Map<ObjectId, UUID> mongoIdToUUID;
public IpLogConverter(Map<ObjectId, UUID> mongoIdToUUID) {
this.mongoIdToUUID = mongoIdToUUID;
}
@Override
public void apply(Document ipLogEntry) {
UUID user = mongoIdToUUID.get(((Map<String, Object>) ipLogEntry.get("user")).get("$id"));
if (user == null || ipLogEntry.getString("ip") == null) {
return;
}
String ip = ipLogEntry.getString("ip").replace("/", "");
if (!IpUtils.isValidIp(ip)) {
return;
}
Date lastSeen = ipLogEntry.getDate("lastSeen");
if (lastSeen == null) {
lastSeen = new Date();
}
IpLogEntry created = new IpLogEntry(
new ObjectId().toString(),
user,
ip,
lastSeen,
lastSeen,
((Number) ipLogEntry.get("uses")).intValue()
);
created.insert();
log.info("Created ip log entry " + created.getId() + " (" + created.getUser() + " - " + created.getUserIp() + ")");
}
}

View File

@ -0,0 +1,59 @@
package net.frozenorb.apiv3.conversion;
import com.google.common.collect.ImmutableMap;
import com.mongodb.Block;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.models.Punishment;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Slf4j
public final class PunishmentConverter implements Block<Document> {
private Map<ObjectId, UUID> mongoIdToUUID;
public PunishmentConverter(Map<ObjectId, UUID> mongoIdToUUID) {
this.mongoIdToUUID = mongoIdToUUID;
}
@Override
public void apply(Document punishment) {
UUID target = mongoIdToUUID.get(((Map<String, Object>) punishment.get("user")).get("$id"));
if (target == null) {
return;
}
// Old punishments have this value set to false to indicate they're not active anymore.
if (punishment.containsKey("active") && !punishment.getBoolean("active")) {
return;
}
Punishment created = new Punishment(
new ObjectId().toString(),
target,
punishment.getString("reason").toString(),
Punishment.PunishmentType.valueOf(punishment.getString("type").toUpperCase()),
punishment.getDate("expires"),
punishment.containsKey("meta") ? (punishment.get("meta") instanceof List ? ImmutableMap.of() : (Document) punishment.get("meta")) : ImmutableMap.of(),
null,
punishment.containsKey("addedBy") ? mongoIdToUUID.get(((Map<String, Object>) punishment.get("addedBy")).get("$id")) : null,
(Date) punishment.getDate("created").clone(),
punishment.containsKey("createdOn") ? String.valueOf(((Map<String, Object>) punishment.get("createdOn")).get("$id")) : "Website",
punishment.containsKey("createdOn") ? ActorType.SERVER : ActorType.WEBSITE,
punishment.containsKey("removedBy") ? (((Map<String, Object>) punishment.get("removedBy")).get("$ref").equals("user") ? mongoIdToUUID.get(((Map<String, Object>) punishment.get("removedBy")).get("$id")) : null) : null,
punishment.containsKey("removedBy") ? (punishment.containsKey("removedAt") ? punishment.getDate("removedAt") : punishment.getDate("created")) : null,
punishment.containsKey("removedBy") ? punishment.getString("removalReason").toString() : null
);
created.insert();
log.info("Created punishment " + created.getId() + " (" + created.getType() + ")");
}
}

View File

@ -0,0 +1,59 @@
package net.frozenorb.apiv3.conversion;
import com.google.common.collect.ImmutableMap;
import com.mongodb.Block;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.Map;
import java.util.UUID;
@Slf4j
public final class UserConverter implements Block<Document> {
private Map<ObjectId, UUID> mongoIdToUUID;
public UserConverter(Map<ObjectId, UUID> mongoIdToUUID) {
this.mongoIdToUUID = mongoIdToUUID;
}
@Override
public void apply(Document user) {
String uuidString = String.valueOf(user.get("uuid"));
if (uuidString == null || uuidString.length() != 32 || user.get("name") == null) {
return;
}
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)) {
return;
}
mongoIdToUUID.put(user.getObjectId("_id"), uuid);
User created = new User(
uuid,
user.get("name").toString(),
ImmutableMap.of(user.get("name").toString(), user.getDate("joined")),
null,
null,
null,
null,
user.getString("email"),
user.getString("phone"),
"INVALID",
user.getDate("joined"),
user.getDate("joined"),
false
);
created.insert();
log.info("Created user " + created.getLastUsername() + " (" + created.getId() + ")");
}
}

View File

@ -0,0 +1,69 @@
package net.frozenorb.apiv3.conversion;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoClients;
import com.mongodb.async.client.MongoDatabase;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import lombok.extern.slf4j.Slf4j;
import org.bson.types.ObjectId;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@Slf4j
public final class V2Converter {
private MongoDatabase importFrom;
public V2Converter(String mongoIp, String database) {
importFrom = MongoClients.create(mongoIp).getDatabase(database);
}
public void startConversion(SingleResultCallback<Void> callback) {
Map<ObjectId, UUID> mongoIdToUUID = new HashMap<>();
importFrom.getCollection("user").find().forEach(new UserConverter(mongoIdToUUID), (ignored, error) -> {
if (error != null) {
callback.onResult(null, error);
return;
}
Future<Void> punishmentsFuture = Future.future();
Future<Void> grantsFuture = Future.future();
Future<Void> ipLogFuture = Future.future();
importFrom.getCollection("punishment").find().forEach(new PunishmentConverter(mongoIdToUUID), new FutureCompatibilityCallback<>(punishmentsFuture));
importFrom.getCollection("grant").find().forEach(new GrantConverter(mongoIdToUUID), new FutureCompatibilityCallback<>(grantsFuture));
importFrom.getCollection("iplog").find().forEach(new IpLogConverter(mongoIdToUUID), new FutureCompatibilityCallback<>(ipLogFuture));
CompositeFuture.all(punishmentsFuture, grantsFuture, ipLogFuture).setHandler((result) -> {
if (result.succeeded()) {
callback.onResult(null, null);
} else {
callback.onResult(null, result.cause());
}
});
});
}
private static class FutureCompatibilityCallback<T> implements SingleResultCallback<T> {
private Future<T> future;
public FutureCompatibilityCallback(Future<T> future) {
this.future = future;
}
public void onResult(T val, Throwable error) {
if (error != null) {
future.fail(error);
} else {
future.complete(val);
}
}
}
}

View File

@ -26,6 +26,7 @@ public final class ActorAttributeHandler implements Handler<RoutingContext> {
} }
} }
// TODO: Cleanup
private void processBasicAuthorization(String authHeader, RoutingContext ctx) { private void processBasicAuthorization(String authHeader, RoutingContext ctx) {
String encodedHeader = authHeader.substring("Basic ".length()); String encodedHeader = authHeader.substring("Basic ".length());
String decodedHeader = new String(Base64.getDecoder().decode(encodedHeader.getBytes())); String decodedHeader = new String(Base64.getDecoder().decode(encodedHeader.getBytes()));
@ -34,24 +35,27 @@ public final class ActorAttributeHandler implements Handler<RoutingContext> {
if (credentials.length == 2) { if (credentials.length == 2) {
User.findByLastUsername(credentials[0], (user, error) -> { User.findByLastUsername(credentials[0], (user, error) -> {
if (error != null) { if (error != null) {
String password = credentials[1]; ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize as " + credentials[0] + ".");
return;
if (user != null && user.getPassword() != null && user.checkPassword(password)) {
ctx.put("actor", new UserActor(user));
ctx.next();
return;
}
} }
ctx.response().putHeader("WWW-Authenticate", "Basic realm=\"MineHQ\""); String password = credentials[1];
ErrorUtils.respondGeneric(ctx, "Failed to authorize as " + credentials[0] + ".");
if (user != null && user.getPassword() != null && user.checkPassword(password)) {
ctx.put("actor", new UserActor(user));
ctx.next();
return;
} else {
ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize as " + credentials[0] + ".");
return;
}
}); });
} else { } else {
ctx.response().putHeader("WWW-Authenticate", "Basic realm=\"MineHQ\""); ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize as " + credentials[0] + ".");
ErrorUtils.respondGeneric(ctx, "Failed to authorize as " + credentials[0] + ".");
} }
} }
// TODO: Cleanup
private void processMHQAuthorization(String authHeader, RoutingContext ctx) { private void processMHQAuthorization(String authHeader, RoutingContext ctx) {
String[] split = authHeader.split(" "); String[] split = authHeader.split(" ");
@ -71,7 +75,7 @@ public final class ActorAttributeHandler implements Handler<RoutingContext> {
Server server = Server.findById(split[1]); Server server = Server.findById(split[1]);
if (server == null) { if (server == null) {
ErrorUtils.respondNotFound(ctx, "Server", split[1]); ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize: Server " + split[1] + " not found");
return; return;
} }
@ -95,7 +99,7 @@ public final class ActorAttributeHandler implements Handler<RoutingContext> {
} }
} }
ErrorUtils.respondGeneric(ctx, "Failed to authorize."); ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize.");
} }
public void processNoAuthorization(RoutingContext ctx) { public void processNoAuthorization(RoutingContext ctx) {

View File

@ -2,7 +2,6 @@ package net.frozenorb.apiv3.handlers;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor; import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
@ -13,9 +12,7 @@ public final class AuthorizationHandler implements Handler<RoutingContext> {
Actor actor = ctx.get("actor"); Actor actor = ctx.get("actor");
if (!actor.isAuthorized()) { if (!actor.isAuthorized()) {
APIv3.getStatsD().incrementCounter("apiv3.http.unauthorized"); ErrorUtils.respondGeneric(ctx, 403, "Please authorize as an approved actor. You're currently authorized as " + actor.getName() + " (" + actor.getType() + ")");
ctx.response().putHeader("WWW-Authenticate", "Basic realm=\"MineHQ\"");
ErrorUtils.respondGeneric(ctx, "Unauthorized access: Please authorize as an approved actor. You're currently authorized as " + actor.getName());
} else { } else {
ctx.next(); ctx.next();
} }

View File

@ -1,15 +0,0 @@
package net.frozenorb.apiv3.handlers;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
public final class MetricsHandler implements Handler<RoutingContext> {
@Override
public void handle(RoutingContext ctx) {
APIv3.getStatsD().incrementCounter("apiv3.http.requests");
ctx.next();
}
}

View File

@ -3,20 +3,20 @@ package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.mongodb.async.SingleResultCallback; import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection; import com.mongodb.async.client.MongoCollection;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id; import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter; import lombok.Getter;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor; import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType; import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType; import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.SyncUtils; import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document; import org.bson.Document;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import java.util.*; import java.util.*;
@Entity
public final class AuditLogEntry { public final class AuditLogEntry {
private static final MongoCollection<AuditLogEntry> auditLogCollection = APIv3.getDatabase().getCollection("auditLog", AuditLogEntry.class); private static final MongoCollection<AuditLogEntry> auditLogCollection = APIv3.getDatabase().getCollection("auditLog", AuditLogEntry.class);
@ -31,11 +31,11 @@ public final class AuditLogEntry {
@Getter private Map<String, Object> metadata; @Getter private Map<String, Object> metadata;
public static List<AuditLogEntry> findAllSync() { public static List<AuditLogEntry> findAllSync() {
return SyncUtils.blockMulti(auditLogCollection.find()); return SyncUtils.blockMulti(auditLogCollection.find().sort(new Document("performedAt", -1)));
} }
public static List<AuditLogEntry> findAllPaginatedSync(int skip, int pageSize) { public static List<AuditLogEntry> findAllPaginatedSync(int skip, int pageSize) {
return SyncUtils.blockMulti(auditLogCollection.find().sort(new Document("performedAt", 1)).skip(skip).limit(pageSize)); return SyncUtils.blockMulti(auditLogCollection.find().sort(new Document("performedAt", -1)).skip(skip).limit(pageSize));
} }
public static AuditLogEntry findByIdSync(String id) { public static AuditLogEntry findByIdSync(String id) {
@ -51,11 +51,11 @@ public final class AuditLogEntry {
} }
public static void findAll(SingleResultCallback<List<AuditLogEntry>> callback) { public static void findAll(SingleResultCallback<List<AuditLogEntry>> callback) {
auditLogCollection.find().into(new ArrayList<>(), callback); auditLogCollection.find().sort(new Document("performedAt", -1)).into(new ArrayList<>(), callback);
} }
public static void findAllPaginated(int skip, int pageSize, SingleResultCallback<List<AuditLogEntry>> callback) { public static void findAllPaginated(int skip, int pageSize, SingleResultCallback<List<AuditLogEntry>> callback) {
auditLogCollection.find().sort(new Document("performedAt", 1)).skip(skip).limit(pageSize).into(new ArrayList<>(), callback); auditLogCollection.find().sort(new Document("performedAt", -1)).skip(skip).limit(pageSize).into(new ArrayList<>(), callback);
} }
public static void findById(String id, SingleResultCallback<AuditLogEntry> callback) { public static void findById(String id, SingleResultCallback<AuditLogEntry> callback) {
@ -83,10 +83,8 @@ public final class AuditLogEntry {
this.metadata = ImmutableMap.copyOf(metadata); this.metadata = ImmutableMap.copyOf(metadata);
} }
public void insert() { public void insert(SingleResultCallback<Void> callback) {
BlockingCallback<Void> callback = new BlockingCallback<>();
auditLogCollection.insertOne(this, callback); auditLogCollection.insertOne(this, callback);
callback.get();
} }
} }

View File

@ -3,7 +3,8 @@ package net.frozenorb.apiv3.models;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import com.mongodb.async.SingleResultCallback; import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection; import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id; import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@ -14,7 +15,9 @@ import org.bson.Document;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
@Entity
@AllArgsConstructor @AllArgsConstructor
public final class Grant { public final class Grant {
@ -35,15 +38,16 @@ public final class Grant {
@Getter private String removalReason; @Getter private String removalReason;
public static List<Grant> findAllSync() { public static List<Grant> findAllSync() {
return SyncUtils.blockMulti(grantsCollection.find()); return SyncUtils.blockMulti(grantsCollection.find().sort(new Document("addedAt", -1)));
} }
public static List<Grant> findAllPaginatedSync(int skip, int pageSize) { public static List<Grant> findAllPaginatedSync(int skip, int pageSize) {
return SyncUtils.blockMulti(grantsCollection.find().sort(new Document("addedAt", 1)).skip(skip).limit(pageSize)); return SyncUtils.blockMulti(grantsCollection.find().sort(new Document("addedAt", -1)).skip(skip).limit(pageSize));
} }
public static List<Grant> findByRankSync(Iterable<Rank> ranks) { public static List<Grant> findByRankSync(Collection<Rank> ranks) {
return SyncUtils.blockMulti(grantsCollection.find(new Document("rank", new Document("$in", ranks)))); Collection<String> convertedRanks = ranks.stream().map(Rank::getId).collect(Collectors.toList());
return SyncUtils.blockMulti(grantsCollection.find(new Document("rank", new Document("$in", convertedRanks))));
} }
public static Grant findByIdSync(String id) { public static Grant findByIdSync(String id) {
@ -59,15 +63,16 @@ public final class Grant {
} }
public static void findAll(SingleResultCallback<List<Grant>> callback) { public static void findAll(SingleResultCallback<List<Grant>> callback) {
grantsCollection.find().into(new ArrayList<>(), callback); grantsCollection.find().sort(new Document("addedAt", -1)).into(new ArrayList<>(), callback);
} }
public static void findAllPaginated(int skip, int pageSize, SingleResultCallback<List<Grant>> callback) { public static void findAllPaginated(int skip, int pageSize, SingleResultCallback<List<Grant>> callback) {
grantsCollection.find().sort(new Document("addedAt", 1)).skip(skip).limit(pageSize).into(new ArrayList<>(), callback); grantsCollection.find().sort(new Document("addedAt", -1)).skip(skip).limit(pageSize).into(new ArrayList<>(), callback);
} }
public static void findByRank(Iterable<Rank> ranks, SingleResultCallback<List<Grant>> callback) { public static void findByRank(Collection<Rank> ranks, SingleResultCallback<List<Grant>> callback) {
grantsCollection.find(new Document("rank", new Document("$in", ranks))).into(new ArrayList<>(), callback); Collection<String> convertedRanks = ranks.stream().map(Rank::getId).collect(Collectors.toList());
grantsCollection.find(new Document("rank", new Document("$in", convertedRanks))).into(new ArrayList<>(), callback);
} }
public static void findById(String id, SingleResultCallback<Grant> callback) { public static void findById(String id, SingleResultCallback<Grant> callback) {
@ -150,8 +155,8 @@ public final class Grant {
this.removedAt = new Date(); this.removedAt = new Date();
this.removalReason = reason; this.removalReason = reason;
BlockingCallback<DeleteResult> callback = new BlockingCallback<>(); BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
grantsCollection.deleteOne(new Document("_id", id), callback); grantsCollection.replaceOne(new Document("_id", id), this, callback);
callback.get(); callback.get();
} }

View File

@ -0,0 +1,171 @@
package net.frozenorb.apiv3.models;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.SyncUtils;
import net.frozenorb.apiv3.utils.TimeUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.*;
@Entity
@AllArgsConstructor
public final class IpBan {
private static final MongoCollection<IpBan> ipBansCollection = APIv3.getDatabase().getCollection("ipBans", IpBan.class);
@Getter @Id private String id;
@Getter private String userIp;
@Getter private String reason;
@Getter private Date expiresAt;
@Getter private UUID addedBy;
@Getter private Date addedAt;
@Getter private String actorName;
@Getter private ActorType actorType;
@Getter private UUID removedBy;
@Getter private Date removedAt;
@Getter private String removalReason;
public static List<IpBan> findAllSync() {
return SyncUtils.blockMulti(ipBansCollection.find().sort(new Document("addedAt", -1)));
}
public static List<IpBan> findAllPaginatedSync(int skip, int pageSize) {
return SyncUtils.blockMulti(ipBansCollection.find().sort(new Document("addedAt", -1)).skip(skip).limit(pageSize));
}
public static IpBan findByIdSync(String id) {
return SyncUtils.blockOne(ipBansCollection.find(new Document("_id", id)));
}
public static List<IpBan> findByIpSync(String userIp) {
return SyncUtils.blockMulti(ipBansCollection.find(new Document("userIp", userIp)));
}
public static void findAll(SingleResultCallback<List<IpBan>> callback) {
ipBansCollection.find().sort(new Document("addedAt", -1)).into(new ArrayList<>(), callback);
}
public static void findAllPaginated(int skip, int pageSize, SingleResultCallback<List<IpBan>> callback) {
ipBansCollection.find().sort(new Document("addedAt", -1)).skip(skip).limit(pageSize).into(new ArrayList<>(), callback);
}
public static void findById(String id, SingleResultCallback<IpBan> callback) {
ipBansCollection.find(new Document("_id", id)).first(callback);
}
public static void findByIp(String userIp, SingleResultCallback<List<IpBan>> callback) {
ipBansCollection.find(new Document("userIp", userIp)).into(new ArrayList<>(), callback);
}
public static void findByIpGrouped(Iterable<String> userIps, SingleResultCallback<Map<String, List<IpBan>>> callback) {
ipBansCollection.find(new Document("userIp", new Document("$in", userIps))).into(new ArrayList<>(), (ipBans, error) -> {
if (error != null) {
callback.onResult(null, error);
} else {
Map<String, List<IpBan>> result = new HashMap<>();
for (String userIp : userIps) {
result.put(userIp, new ArrayList<>());
}
for (IpBan ipBan : ipBans) {
result.get(ipBan.getUserIp()).add(ipBan);
}
callback.onResult(result, null);
}
});
}
public IpBan() {} // For Morphia
public IpBan(String userIp, Punishment linked) {
this.id = new ObjectId().toString();
this.userIp = userIp;
this.reason = linked.getReason();
this.expiresAt = linked.getExpiresAt();
this.addedBy = linked.getAddedBy();
this.addedAt = new Date();
this.actorName = linked.getActorName();
this.actorType = linked.getActorType();
}
public IpBan(String userIp, String reason, Date expiresAt, User addedBy, Actor actor) {
this.id = new ObjectId().toString();
this.userIp = userIp;
this.reason = reason;
this.expiresAt = expiresAt;
this.addedBy = addedBy == null ? null : addedBy.getId();
this.addedAt = new Date();
this.actorName = actor.getName();
this.actorType = actor.getType();
}
public boolean isActive() {
return !(isExpired() || isRemoved());
}
public boolean isExpired() {
if (expiresAt == null) {
return false; // Never expires
} else {
return expiresAt.before(new Date());
}
}
public boolean isRemoved() {
return removedBy != null;
}
public String getAccessDenialReason() {
String accessDenialReason = "Your ip address has been suspended from the MineHQ Network. \n\n";
if (getExpiresAt() != null) {
accessDenialReason += "Expires in " + TimeUtils.formatIntoDetailedString(TimeUtils.getSecondsBetween(getExpiresAt(), new Date()));
} else {
accessDenialReason += "Appeal at MineHQ.com/appeal";
}
return accessDenialReason;
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
ipBansCollection.insertOne(this, callback);
callback.get();
}
public void delete(User removedBy, String reason) {
this.removedBy = removedBy.getId();
this.removedAt = new Date();
this.removalReason = reason;
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
ipBansCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
public void delete(Punishment linkedPunishment) {
this.removedBy = linkedPunishment.getRemovedBy();
this.removedAt = new Date();
this.removalReason = "Linked punishment removed: " + linkedPunishment.getRemovalReason();
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
ipBansCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
}

View File

@ -3,6 +3,7 @@ package net.frozenorb.apiv3.models;
import com.mongodb.async.SingleResultCallback; import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection; import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.UpdateResult; import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id; import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@ -17,10 +18,11 @@ import java.util.Date;
import java.util.List; import java.util.List;
import java.util.UUID; import java.util.UUID;
@Entity
@AllArgsConstructor @AllArgsConstructor
public final class IPLogEntry { public final class IpLogEntry {
private static final MongoCollection<IPLogEntry> ipLogCollection = APIv3.getDatabase().getCollection("ipLog", IPLogEntry.class); private static final MongoCollection<IpLogEntry> ipLogCollection = APIv3.getDatabase().getCollection("ipLog", IpLogEntry.class);
@Getter @Id private String id; @Getter @Id private String id;
@Getter private UUID user; @Getter private UUID user;
@ -29,57 +31,57 @@ public final class IPLogEntry {
@Getter private Date lastSeenAt; @Getter private Date lastSeenAt;
@Getter private int uses; @Getter private int uses;
public static List<IPLogEntry> findAllSync() { public static List<IpLogEntry> findAllSync() {
return SyncUtils.blockMulti(ipLogCollection.find()); return SyncUtils.blockMulti(ipLogCollection.find().sort(new Document("lastSeenAt", -1)));
} }
public static IPLogEntry findByIdSync(String id) { public static IpLogEntry findByIdSync(String id) {
return SyncUtils.blockOne(ipLogCollection.find(new Document("_id", id))); return SyncUtils.blockOne(ipLogCollection.find(new Document("_id", id)));
} }
public static List<IPLogEntry> findByUserSync(User user) { public static List<IpLogEntry> findByUserSync(User user) {
return findByUserSync(user.getId()); return findByUserSync(user.getId());
} }
public static List<IPLogEntry> findByUserSync(UUID user) { public static List<IpLogEntry> findByUserSync(UUID user) {
return SyncUtils.blockMulti(ipLogCollection.find(new Document("user", user))); return SyncUtils.blockMulti(ipLogCollection.find(new Document("user", user)).sort(new Document("lastSeenAt", -1)));
} }
public static IPLogEntry findByUserAndIpSync(User user, String userIp) { public static IpLogEntry findByUserAndIpSync(User user, String userIp) {
return findByUserAndIpSync(user.getId(), userIp); return findByUserAndIpSync(user.getId(), userIp);
} }
public static IPLogEntry findByUserAndIpSync(UUID user, String userIp) { public static IpLogEntry findByUserAndIpSync(UUID user, String userIp) {
return SyncUtils.blockOne(ipLogCollection.find(new Document("user", user).append("userIp", userIp))); return SyncUtils.blockOne(ipLogCollection.find(new Document("user", user).append("userIp", userIp)));
} }
public static void findAll(SingleResultCallback<List<IPLogEntry>> callback) { public static void findAll(SingleResultCallback<List<IpLogEntry>> callback) {
ipLogCollection.find().into(new ArrayList<>(), callback); ipLogCollection.find().sort(new Document("lastSeenAt", -1)).into(new ArrayList<>(), callback);
} }
public static void findById(String id, SingleResultCallback<IPLogEntry> callback) { public static void findById(String id, SingleResultCallback<IpLogEntry> callback) {
ipLogCollection.find(new Document("_id", id)).first(callback); ipLogCollection.find(new Document("_id", id)).first(callback);
} }
public static void findByUser(User user, SingleResultCallback<List<IPLogEntry>> callback) { public static void findByUser(User user, SingleResultCallback<List<IpLogEntry>> callback) {
findByUser(user.getId(), callback); findByUser(user.getId(), callback);
} }
public static void findByUser(UUID user, SingleResultCallback<List<IPLogEntry>> callback) { public static void findByUser(UUID user, SingleResultCallback<List<IpLogEntry>> callback) {
ipLogCollection.find(new Document("user", user)).into(new ArrayList<>(), callback); ipLogCollection.find(new Document("user", user)).sort(new Document("lastSeenAt", -1)).into(new ArrayList<>(), callback);
} }
public static void findByUserAndIp(User user, String userIp, SingleResultCallback<IPLogEntry> callback) { public static void findByUserAndIp(User user, String userIp, SingleResultCallback<IpLogEntry> callback) {
findByUserAndIp(user.getId(), userIp, callback); findByUserAndIp(user.getId(), userIp, callback);
} }
public static void findByUserAndIp(UUID user, String userIp, SingleResultCallback<IPLogEntry> callback) { public static void findByUserAndIp(UUID user, String userIp, SingleResultCallback<IpLogEntry> callback) {
ipLogCollection.find(new Document("user", user).append("userIp", userIp)).first(callback); ipLogCollection.find(new Document("user", user).append("userIp", userIp)).first(callback);
} }
public IPLogEntry() {} // For Morphia public IpLogEntry() {} // For Morphia
public IPLogEntry(User user, String userIp) { public IpLogEntry(User user, String userIp) {
this.id = new ObjectId().toString(); this.id = new ObjectId().toString();
this.user = user.getId(); this.user = user.getId();
this.userIp = userIp; this.userIp = userIp;

View File

@ -4,12 +4,12 @@ import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection; import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult; import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id; import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.unsorted.BlockingCallback; import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.unsorted.Notification;
import net.frozenorb.apiv3.utils.SyncUtils; import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document; import org.bson.Document;
@ -17,6 +17,7 @@ import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@Entity
public final class NotificationTemplate { public final class NotificationTemplate {
private static final MongoCollection<NotificationTemplate> notificationTemplatesCollection = APIv3.getDatabase().getCollection("notificationTemplates", NotificationTemplate.class); private static final MongoCollection<NotificationTemplate> notificationTemplatesCollection = APIv3.getDatabase().getCollection("notificationTemplates", NotificationTemplate.class);

View File

@ -2,7 +2,8 @@ package net.frozenorb.apiv3.models;
import com.mongodb.async.SingleResultCallback; import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection; import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id; import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
@ -16,7 +17,9 @@ import org.bson.Document;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import java.util.*; import java.util.*;
import java.util.stream.Collectors;
@Entity
@AllArgsConstructor @AllArgsConstructor
public final class Punishment { public final class Punishment {
@ -28,6 +31,7 @@ public final class Punishment {
@Getter private PunishmentType type; @Getter private PunishmentType type;
@Getter private Date expiresAt; @Getter private Date expiresAt;
@Getter private Map<String, Object> metadata; @Getter private Map<String, Object> metadata;
@Getter private String linkedIpBanId;
@Getter private UUID addedBy; @Getter private UUID addedBy;
@Getter private Date addedAt; @Getter private Date addedAt;
@ -39,15 +43,16 @@ public final class Punishment {
@Getter private String removalReason; @Getter private String removalReason;
public static List<Punishment> findAllSync() { public static List<Punishment> findAllSync() {
return SyncUtils.blockMulti(punishmentsCollection.find()); return SyncUtils.blockMulti(punishmentsCollection.find().sort(new Document("addedAt", -1)));
} }
public static List<Punishment> findAllPaginatedSync(int skip, int pageSize) { public static List<Punishment> findAllPaginatedSync(int skip, int pageSize) {
return SyncUtils.blockMulti(punishmentsCollection.find().sort(new Document("addedAt", 1)).skip(skip).limit(pageSize)); return SyncUtils.blockMulti(punishmentsCollection.find().sort(new Document("addedAt", -1)).skip(skip).limit(pageSize));
} }
public static List<Punishment> findByTypeSync(Iterable<PunishmentType> types) { public static List<Punishment> findByTypeSync(Collection<PunishmentType> types) {
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("type", new Document("$in", types)))); Collection<String> convertedTypes = types.stream().map(PunishmentType::name).collect(Collectors.toList());
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("type", new Document("$in", convertedTypes))));
} }
public static Punishment findByIdSync(String id) { public static Punishment findByIdSync(String id) {
@ -62,24 +67,26 @@ public final class Punishment {
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("user", user))); return SyncUtils.blockMulti(punishmentsCollection.find(new Document("user", user)));
} }
public static List<Punishment> findByUserAndTypeSync(User user, Iterable<PunishmentType> types) { public static List<Punishment> findByUserAndTypeSync(User user, Collection<PunishmentType> types) {
return findByUserAndTypeSync(user.getId(), types); return findByUserAndTypeSync(user.getId(), types);
} }
public static List<Punishment> findByUserAndTypeSync(UUID user, Iterable<PunishmentType> types) { public static List<Punishment> findByUserAndTypeSync(UUID user, Collection<PunishmentType> types) {
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("user", user).append("type", new Document("$in", types)))); Collection<String> convertedTypes = types.stream().map(PunishmentType::name).collect(Collectors.toList());
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("user", user).append("type", new Document("$in", convertedTypes))));
} }
public static void findAll(SingleResultCallback<List<Punishment>> callback) { public static void findAll(SingleResultCallback<List<Punishment>> callback) {
punishmentsCollection.find().into(new ArrayList<>(), callback); punishmentsCollection.find().sort(new Document("addedAt", -1)).into(new ArrayList<>(), callback);
} }
public static void findAllPaginated(int skip, int pageSize, SingleResultCallback<List<Punishment>> callback) { public static void findAllPaginated(int skip, int pageSize, SingleResultCallback<List<Punishment>> callback) {
punishmentsCollection.find().sort(new Document("addedAt", 1)).skip(skip).limit(pageSize).into(new ArrayList<>(), callback); punishmentsCollection.find().sort(new Document("addedAt", -1)).skip(skip).limit(pageSize).into(new ArrayList<>(), callback);
} }
public static void findByType(Iterable<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) { public static void findByType(Collection<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) {
punishmentsCollection.find(new Document("type", new Document("$in", types))).into(new ArrayList<>(), callback); Collection<String> convertedTypes = types.stream().map(PunishmentType::name).collect(Collectors.toList());
punishmentsCollection.find(new Document("type", new Document("$in", convertedTypes))).into(new ArrayList<>(), callback);
} }
public static void findById(String id, SingleResultCallback<Punishment> callback) { public static void findById(String id, SingleResultCallback<Punishment> callback) {
@ -114,12 +121,13 @@ public final class Punishment {
}); });
} }
public static void findByUserAndType(User user, Iterable<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) { public static void findByUserAndType(User user, Collection<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) {
findByUserAndType(user.getId(), types, callback); findByUserAndType(user.getId(), types, callback);
} }
public static void findByUserAndType(UUID user, Iterable<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) { public static void findByUserAndType(UUID user, Collection<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) {
punishmentsCollection.find(new Document("user", user).append("type", new Document("$in", types))).into(new ArrayList<>(), callback); Collection<String> convertedTypes = types.stream().map(PunishmentType::name).collect(Collectors.toList());
punishmentsCollection.find(new Document("user", user).append("type", new Document("$in", convertedTypes))).into(new ArrayList<>(), callback);
} }
public Punishment() {} // For Morphia public Punishment() {} // For Morphia
@ -172,6 +180,10 @@ public final class Punishment {
} }
} }
public void linkIpBan(IpBan ipBan) {
}
public void insert() { public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>(); BlockingCallback<Void> callback = new BlockingCallback<>();
punishmentsCollection.insertOne(this, callback); punishmentsCollection.insertOne(this, callback);
@ -183,8 +195,8 @@ public final class Punishment {
this.removedAt = new Date(); this.removedAt = new Date();
this.removalReason = reason; this.removalReason = reason;
BlockingCallback<DeleteResult> callback = new BlockingCallback<>(); BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
punishmentsCollection.deleteOne(new Document("_id", id), callback); punishmentsCollection.replaceOne(new Document("_id", id), this, callback);
callback.get(); callback.get();
} }

View File

@ -5,6 +5,7 @@ import com.google.common.primitives.Ints;
import com.mongodb.async.client.MongoCollection; import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult; import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id; import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter; import lombok.Getter;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
@ -18,6 +19,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Entity
public final class Rank { public final class Rank {
private static final MongoCollection<Rank> ranksCollection = APIv3.getDatabase().getCollection("ranks", Rank.class); private static final MongoCollection<Rank> ranksCollection = APIv3.getDatabase().getCollection("ranks", Rank.class);

View File

@ -4,11 +4,12 @@ import com.google.common.collect.ImmutableSet;
import com.mongodb.async.client.MongoCollection; import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult; import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id; import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies; import net.frozenorb.apiv3.serialization.gson.ExcludeFromReplies;
import net.frozenorb.apiv3.unsorted.BlockingCallback; import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.SyncUtils; import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document; import org.bson.Document;
@ -16,6 +17,7 @@ import org.bson.Document;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Entity
public final class Server { public final class Server {
private static final MongoCollection<Server> serversCollection = APIv3.getDatabase().getCollection("servers", Server.class); private static final MongoCollection<Server> serversCollection = APIv3.getDatabase().getCollection("servers", Server.class);

View File

@ -1,14 +1,14 @@
package net.frozenorb.apiv3.models; package net.frozenorb.apiv3.models;
import com.google.gson.annotations.SerializedName;
import com.mongodb.async.client.MongoCollection; import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult; import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id; import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies; import net.frozenorb.apiv3.serialization.gson.ExcludeFromReplies;
import net.frozenorb.apiv3.unsorted.BlockingCallback; import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.PermissionUtils; import net.frozenorb.apiv3.utils.PermissionUtils;
import net.frozenorb.apiv3.utils.SyncUtils; import net.frozenorb.apiv3.utils.SyncUtils;
@ -17,6 +17,7 @@ import org.bson.Document;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Entity
public final class ServerGroup { public final class ServerGroup {
private static final MongoCollection<ServerGroup> serverGroupsCollection = APIv3.getDatabase().getCollection("serverGroups", ServerGroup.class); private static final MongoCollection<ServerGroup> serverGroupsCollection = APIv3.getDatabase().getCollection("serverGroups", ServerGroup.class);
@ -32,6 +33,7 @@ public final class ServerGroup {
@Getter @Setter @ExcludeFromReplies private Set<String> announcements = new HashSet<>(); @Getter @Setter @ExcludeFromReplies private Set<String> announcements = new HashSet<>();
@Getter @Setter @ExcludeFromReplies private Map<String, List<String>> permissions = new HashMap<>(); @Getter @Setter @ExcludeFromReplies private Map<String, List<String>> permissions = new HashMap<>();
// make this and other stuff async
public static List<ServerGroup> findAll() { public static List<ServerGroup> findAll() {
updateCacheIfNeeded(); updateCacheIfNeeded();
return serverGroupAltCache; return serverGroupAltCache;

View File

@ -1,5 +1,6 @@
package net.frozenorb.apiv3.models; package net.frozenorb.apiv3.models;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
@ -9,14 +10,17 @@ import com.google.common.hash.Hashing;
import com.mongodb.async.SingleResultCallback; import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection; import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.UpdateResult; import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id; import fr.javatic.mongo.jacksonCodec.objectId.Id;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies; import net.frozenorb.apiv3.serialization.gson.ExcludeFromReplies;
import net.frozenorb.apiv3.serialization.UUIDJsonDeserializer; import net.frozenorb.apiv3.serialization.jackson.UUIDJsonDeserializer;
import net.frozenorb.apiv3.serialization.UUIDJsonSerializer; import net.frozenorb.apiv3.serialization.jackson.UUIDJsonSerializer;
import net.frozenorb.apiv3.unsorted.BlockingCallback; import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.MojangUtils; import net.frozenorb.apiv3.utils.MojangUtils;
import net.frozenorb.apiv3.utils.PermissionUtils; import net.frozenorb.apiv3.utils.PermissionUtils;
@ -24,8 +28,10 @@ import net.frozenorb.apiv3.utils.SyncUtils;
import net.frozenorb.apiv3.utils.UUIDUtils; import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.Document; import org.bson.Document;
import java.io.IOException;
import java.util.*; import java.util.*;
@Entity
@AllArgsConstructor @AllArgsConstructor
public final class User { public final class User {
@ -46,7 +52,7 @@ public final class User {
@Getter private boolean online; @Getter private boolean online;
public static List<User> findAllSync() { public static List<User> findAllSync() {
return SyncUtils.blockMulti(usersCollection.find()); return SyncUtils.blockMulti(usersCollection.find().sort(new Document("lastSeenAt", -1)));
} }
public static User findByIdSync(String id) { public static User findByIdSync(String id) {
@ -78,7 +84,7 @@ public final class User {
} }
public static void findAll(SingleResultCallback<List<User>> callback) { public static void findAll(SingleResultCallback<List<User>> callback) {
usersCollection.find().into(new ArrayList<>(), callback); usersCollection.find().sort(new Document("lastSeenAt", -1)).into(new ArrayList<>(), callback);
} }
public static void findById(String id, SingleResultCallback<User> callback) { public static void findById(String id, SingleResultCallback<User> callback) {
@ -140,7 +146,10 @@ public final class User {
this.lastSeenAt = new Date(); this.lastSeenAt = new Date();
this.firstSeenAt = new Date(); this.firstSeenAt = new Date();
updateUsername(lastUsername); // TODO: MAKE THIS ASYNC? SOMEHOW?
BlockingCallback<Void> blockingCallback = new BlockingCallback<>();
updateUsername(lastUsername, blockingCallback);
blockingCallback.get();
} }
public boolean hasPermissionAnywhere(String permission) { public boolean hasPermissionAnywhere(String permission) {
@ -180,21 +189,31 @@ public final class User {
this.online = false; this.online = false;
} }
public void updateUsername(String username) { public void updateUsername(String newUsername, SingleResultCallback<Void> callback) {
if (!username.equals(lastUsername)) { this.aliases.put(newUsername, new Date());
this.lastUsername = username;
User withNewUsername; if (newUsername.equalsIgnoreCase(lastUsername)) {
callback.onResult(null, null);
while ((withNewUsername = User.findByLastUsernameSync(username)) != null) { return;
BlockingCallback<String> callback = new BlockingCallback<>();
MojangUtils.getName(withNewUsername.getId(), callback);
String newUsername = callback.get();
withNewUsername.updateUsername(newUsername);
}
} }
this.aliases.put(username, new Date()); this.lastUsername = newUsername;
User.findByLastUsername(newUsername, (otherUser, error) -> {
if (error != null) {
callback.onResult(null, error);
} else if (otherUser != null) {
MojangUtils.getName(otherUser.getId(), (newName, error2) -> {
if (error2 != null) {
callback.onResult(null, error2);
} else {
otherUser.updateUsername(newName, callback);
}
});
} else {
callback.onResult(null, null);
}
});
} }
public void setPassword(String input) { public void setPassword(String input) {
@ -272,22 +291,46 @@ public final class User {
return highestRanks; return highestRanks;
} }
public Map<String, Object> getLoginInfo(Server server) { public void getLoginInfo(Server server, SingleResultCallback<Map<String, Object>> callback) {
return createLoginInfo( Future<Iterable<Punishment>> punishmentsFuture = Future.future();
server, Future<Iterable<Grant>> grantsFuture = Future.future();
Punishment.findByUserAndTypeSync(this, ImmutableSet.of(
Punishment.PunishmentType.BLACKLIST, Punishment.findByUserAndType(this, ImmutableSet.of(
Punishment.PunishmentType.BAN, Punishment.PunishmentType.BLACKLIST,
Punishment.PunishmentType.MUTE Punishment.PunishmentType.BAN,
)), Punishment.PunishmentType.MUTE
Grant.findByUserSync(this) ), (punishments, error) -> {
); if (error != null) {
punishmentsFuture.fail(error);
} else {
punishmentsFuture.complete(punishments);
}
});
Grant.findByUser(this, (grants, error) -> {
if (error != null) {
grantsFuture.fail(error);
} else {
grantsFuture.complete(grants);
}
});
CompositeFuture.all(punishmentsFuture, grantsFuture).setHandler((result) -> {
if (result.succeeded()) {
Iterable<Punishment> punishments = result.result().result(0);
Iterable<Grant> grants = result.result().result(1);
callback.onResult(createLoginInfo(server, punishments, grants), null);
} else {
callback.onResult(null, result.cause());
}
});
} }
// This is only used to help batch requests to mongo // This is only used to help batch requests to mongo
public Map<String, Object> createLoginInfo(Server server, Iterable<Punishment> punishments, Iterable<Grant> grants) { public Map<String, Object> createLoginInfo(Server server, Iterable<Punishment> punishments, Iterable<Grant> grants) {
Punishment activeMute = null; Punishment activeMute = null;
String accessDenialReason = null; Punishment activeBan = null;
for (Punishment punishment : punishments) { for (Punishment punishment : punishments) {
if (!punishment.isActive()) { if (!punishment.isActive()) {
@ -296,8 +339,8 @@ public final class User {
if (punishment.getType() == Punishment.PunishmentType.MUTE) { if (punishment.getType() == Punishment.PunishmentType.MUTE) {
activeMute = punishment; activeMute = punishment;
} else { } else if (punishment.getType() == Punishment.PunishmentType.BAN || punishment.getType() == Punishment.PunishmentType.BLACKLIST) {
accessDenialReason = punishment.getAccessDenialReason(); activeBan = punishment;
} }
} }
@ -307,8 +350,9 @@ public final class User {
ImmutableMap.Builder<String, Object> result = ImmutableMap.<String, Object>builder() ImmutableMap.Builder<String, Object> result = ImmutableMap.<String, Object>builder()
.put("user", this) .put("user", this)
.put("access", ImmutableMap.of( .put("access", ImmutableMap.of(
"allowed", accessDenialReason == null, "allowed", activeBan == null,
"message", accessDenialReason == null ? "Public server" : accessDenialReason "message", activeBan == null ? "Public server" : activeBan.getAccessDenialReason(),
"activeBanId", activeBan == null ? "" : activeBan.getId()
)) ))
.put("rank", highestRank.getId()) .put("rank", highestRank.getId())
.put("totpSetup", getTotpSecret() != null); .put("totpSetup", getTotpSecret() != null);
@ -328,8 +372,12 @@ public final class User {
public void save() { public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>(); BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
usersCollection.replaceOne(new Document("_id", id), this, callback); save(callback);
callback.get(); callback.get();
} }
public void save(SingleResultCallback<UpdateResult> callback) {
usersCollection.replaceOne(new Document("_id", id), this, callback);
}
} }

View File

@ -5,6 +5,7 @@ import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection; import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult; import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult; import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id; import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
@ -19,6 +20,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
@Entity
public final class UserMetaEntry { public final class UserMetaEntry {
private static final MongoCollection<UserMetaEntry> userMetaCollection = APIv3.getDatabase().getCollection("userMeta", UserMetaEntry.class); private static final MongoCollection<UserMetaEntry> userMetaCollection = APIv3.getDatabase().getCollection("userMeta", UserMetaEntry.class);

View File

@ -18,6 +18,7 @@ public final class GETDump implements Handler<RoutingContext> {
private Map<String, List<UUID>> grantCache = new HashMap<>(); private Map<String, List<UUID>> grantCache = new HashMap<>();
public GETDump() { public GETDump() {
// TODO: Use vertx scheduler
Thread dumpUpdater = new Thread() { Thread dumpUpdater = new Thread() {
@Override @Override
@ -69,7 +70,7 @@ public final class GETDump implements Handler<RoutingContext> {
try { try {
Thread.sleep(TimeUnit.MINUTES.toMillis(3)); Thread.sleep(TimeUnit.MINUTES.toMillis(3));
} catch (Exception ex) { } catch (InterruptedException ex) {
throw new RuntimeException(ex); throw new RuntimeException(ex);
} }

View File

@ -3,27 +3,20 @@ package net.frozenorb.apiv3.routes.announcements;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.actors.ServerActor;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup; import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETAnnouncements implements Handler<RoutingContext> { public final class GETAnnouncements implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) { public void handle(RoutingContext ctx) {
Actor actor = ctx.get("actor"); ServerGroup serverGroup = ServerGroup.findById(ctx.request().getParam("id"));
if (actor.getType() != ActorType.SERVER) { if (serverGroup == null) {
ErrorUtils.respondServerOnly(ctx); ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("id"));
return; return;
} }
Server sender = ((ServerActor) actor).getServer(); APIv3.respondJson(ctx, serverGroup.getAnnouncements());
ServerGroup senderGroup = ServerGroup.findById(sender.getServerGroup());
APIv3.respondJson(ctx, senderGroup.getAnnouncements());
} }
} }

View File

@ -0,0 +1,33 @@
package net.frozenorb.apiv3.routes.announcements;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.utils.ErrorUtils;
import java.util.HashSet;
import java.util.Set;
public final class PUTAnnouncements implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
ServerGroup serverGroup = ServerGroup.findById(ctx.request().getParam("id"));
if (serverGroup == null) {
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("id"));
return;
}
Set<String> announcements = new HashSet<>();
for (Object announcement : ctx.getBodyAsJsonArray()) {
announcements.add((String) announcement);
}
serverGroup.setAnnouncements(announcements);
serverGroup.save();
APIv3.respondJson(ctx, serverGroup.getAnnouncements());
}
}

View File

@ -0,0 +1,47 @@
package net.frozenorb.apiv3.routes.auditLog;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.models.AuditLogEntry;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IpUtils;
import org.bson.Document;
public final class POSTUserAuditLogEntry implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
String userIp = ctx.request().getParam("userIp");
if (!IpUtils.isValidIp(userIp)) {
ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + userIp + "\" is not valid.");
return;
}
AuditLogActionType type;
try {
type = AuditLogActionType.valueOf(ctx.request().getParam("type"));
} catch (IllegalArgumentException ex) {
ErrorUtils.respondNotFound(ctx, "Audit log action type", ctx.request().getParam("type"));
return;
}
BlockingCallback<AuditLogEntry> blockingCallback = new BlockingCallback<>();
AuditLog.log(user, userIp, ctx.get("actor"), type, Document.parse(ctx.getBodyAsString()), blockingCallback);
AuditLogEntry entry = blockingCallback.get();
APIv3.respondJson(ctx, entry);
}
}

View File

@ -1,6 +1,6 @@
package net.frozenorb.apiv3.routes.chatFilterList; package net.frozenorb.apiv3.routes.chatFilterList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableList;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
@ -8,7 +8,9 @@ import net.frozenorb.apiv3.APIv3;
public final class GETChatFilterList implements Handler<RoutingContext> { public final class GETChatFilterList implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) { public void handle(RoutingContext ctx) {
APIv3.respondJson(ctx, ImmutableMap.of()); // TODO
// WARNING: THIS IS REGISTERED ASYNC SO DONT MESS IT UP
APIv3.respondJson(ctx, ImmutableList.of());
} }
} }

View File

@ -6,8 +6,10 @@ import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.auditLog.AuditLog; import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType; import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.models.AuditLogEntry;
import net.frozenorb.apiv3.models.Grant; import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETEGrant implements Handler<RoutingContext> { public final class DELETEGrant implements Handler<RoutingContext> {
@ -38,7 +40,9 @@ public final class DELETEGrant implements Handler<RoutingContext> {
} }
grant.delete(removedBy, reason); grant.delete(removedBy, reason);
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_GRANT, ImmutableMap.of()); BlockingCallback<AuditLogEntry> blockingCallback = new BlockingCallback<>();
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_GRANT, ImmutableMap.of(), blockingCallback);
blockingCallback.get();
APIv3.respondJson(ctx, grant); APIv3.respondJson(ctx, grant);
} }

View File

@ -0,0 +1,49 @@
package net.frozenorb.apiv3.routes.ipBans;
import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.models.AuditLogEntry;
import net.frozenorb.apiv3.models.IpBan;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETEIpBan implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
IpBan ipBan = IpBan.findByIdSync(ctx.request().getParam("id"));
if (ipBan == null) {
ErrorUtils.respondNotFound(ctx, "IpBan", ctx.request().getParam("id"));
return;
} else if (!ipBan.isActive()) {
ErrorUtils.respondInvalidInput(ctx, "Cannot remove an inactive ip ban.");
return;
}
User removedBy = User.findByIdSync(ctx.request().getParam("removedBy"));
if (removedBy == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("removedBy"));
return;
}
String reason = ctx.request().getParam("reason");
if (reason == null || reason.trim().isEmpty()) {
ErrorUtils.respondRequiredInput(ctx, "reason");
return;
}
ipBan.delete(removedBy, reason);
BlockingCallback<AuditLogEntry> blockingCallback = new BlockingCallback<>();
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of(), blockingCallback);
blockingCallback.get();
APIv3.respondJson(ctx, ipBan);
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.ipBans;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.IpBan;
public final class GETIpBan implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
APIv3.respondJson(ctx, IpBan.findByIdSync(ctx.request().getParam("id")));
}
}

View File

@ -0,0 +1,22 @@
package net.frozenorb.apiv3.routes.ipBans;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.IpBan;
import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETIpBans implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
try {
int skip = ctx.request().getParam("skip") == null ? 0 : Integer.parseInt(ctx.request().getParam("skip"));
int pageSize = ctx.request().getParam("pageSize") == null ? 100 : Integer.parseInt(ctx.request().getParam("pageSize"));
APIv3.respondJson(ctx, IpBan.findAllPaginatedSync(skip, pageSize));
} catch (NumberFormatException ex) {
ErrorUtils.respondInvalidInput(ctx, "skip and pageSize must be numerical inputs.");
}
}
}

View File

@ -0,0 +1,24 @@
package net.frozenorb.apiv3.routes.ipBans;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.IpBan;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IpUtils;
public final class GETIpIpBans implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
String userIp = ctx.request().getParam("id");
if (!IpUtils.isValidIp(userIp)) {
ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + userIp + "\" is not valid.");
return;
}
APIv3.respondJson(ctx, IpBan.findByIpSync(userIp));
}
}

View File

@ -0,0 +1,57 @@
package net.frozenorb.apiv3.routes.ipBans;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.IpBan;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Permissions;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IpUtils;
import org.bson.Document;
import java.util.Date;
import java.util.Map;
public final class POSTIpIpBan implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
String userIp = ctx.request().getParam("id");
if (!IpUtils.isValidIp(userIp)) {
ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + userIp + "\" is not valid.");
return;
}
String reason = ctx.request().getParam("reason");
if (reason == null || reason.trim().isEmpty()) {
ErrorUtils.respondRequiredInput(ctx, "reason");
return;
}
Date expiresAt;
try {
expiresAt = new Date(Long.parseLong(ctx.request().getParam("expiresAt")));
} catch (NumberFormatException ex) {
expiresAt = null;
}
if (expiresAt != null && expiresAt.before(new Date())) {
ErrorUtils.respondInvalidInput(ctx, "Expiration date cannot be in the past.");
return;
}
// We purposely don't do a null check, ip bans don't have to have a source.
User addedBy = User.findByIdSync(ctx.request().getParam("addedBy"));
IpBan ipBan = new IpBan(userIp, reason, expiresAt, addedBy, ctx.get("actor"));
ipBan.insert();
APIv3.respondJson(ctx, ipBan);
}
}

View File

@ -3,11 +3,11 @@ package net.frozenorb.apiv3.routes.ipLog;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.IPLogEntry; import net.frozenorb.apiv3.models.IpLogEntry;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserIPLog implements Handler<RoutingContext> { public final class GETUserIpLog implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) { public void handle(RoutingContext ctx) {
User target = User.findByIdSync(ctx.request().getParam("id")); User target = User.findByIdSync(ctx.request().getParam("id"));
@ -17,7 +17,7 @@ public final class GETUserIPLog implements Handler<RoutingContext> {
return; return;
} }
APIv3.respondJson(ctx, IPLogEntry.findByUserSync(target)); APIv3.respondJson(ctx, IpLogEntry.findByUserSync(target));
} }
} }

View File

@ -1,4 +0,0 @@
package net.frozenorb.apiv3.routes.notificationTemplate;
public class PUTNotificationTemplate {
}

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.routes.notificationTemplate; package net.frozenorb.apiv3.routes.notificationTemplates;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.routes.notificationTemplate; package net.frozenorb.apiv3.routes.notificationTemplates;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.routes.notificationTemplate; package net.frozenorb.apiv3.routes.notificationTemplates;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.routes.notificationTemplate; package net.frozenorb.apiv3.routes.notificationTemplates;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;

View File

@ -0,0 +1,4 @@
package net.frozenorb.apiv3.routes.notificationTemplates;
public class PUTNotificationTemplate {
}

View File

@ -6,8 +6,10 @@ import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.auditLog.AuditLog; import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType; import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.models.AuditLogEntry;
import net.frozenorb.apiv3.models.Punishment; import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETEPunishment implements Handler<RoutingContext> { public final class DELETEPunishment implements Handler<RoutingContext> {
@ -38,7 +40,9 @@ public final class DELETEPunishment implements Handler<RoutingContext> {
} }
punishment.delete(removedBy, reason); punishment.delete(removedBy, reason);
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of()); BlockingCallback<AuditLogEntry> blockingCallback = new BlockingCallback<>();
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of(), blockingCallback);
blockingCallback.get();
APIv3.respondJson(ctx, punishment); APIv3.respondJson(ctx, punishment);
} }

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.routes.users; package net.frozenorb.apiv3.routes.punishments;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -7,8 +7,10 @@ import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.auditLog.AuditLog; import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType; import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.models.AuditLogEntry;
import net.frozenorb.apiv3.models.Punishment; import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETEUserPunishment implements Handler<RoutingContext> { public final class DELETEUserPunishment implements Handler<RoutingContext> {
@ -39,13 +41,15 @@ public final class DELETEUserPunishment implements Handler<RoutingContext> {
for (Punishment punishment : Punishment.findByUserAndTypeSync(target, ImmutableSet.of(type))) { for (Punishment punishment : Punishment.findByUserAndTypeSync(target, ImmutableSet.of(type))) {
if (punishment.isActive()) { if (punishment.isActive()) {
punishment.delete(removedBy, reason); punishment.delete(removedBy, reason);
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of()); BlockingCallback<AuditLogEntry> blockingCallback = new BlockingCallback<>();
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of(), blockingCallback);
blockingCallback.get();
APIv3.respondJson(ctx, punishment); APIv3.respondJson(ctx, punishment);
return; return;
} }
} }
ErrorUtils.respondGeneric(ctx, "User provided has no active punishments"); ErrorUtils.respondGeneric(ctx, 404, "User provided has no active punishments");
} }
} }

View File

@ -36,7 +36,7 @@ public final class POSTUserPunish implements Handler<RoutingContext> {
if (type != Punishment.PunishmentType.WARN) { if (type != Punishment.PunishmentType.WARN) {
for (Punishment punishment : Punishment.findByUserAndTypeSync(target, ImmutableSet.of(type))) { for (Punishment punishment : Punishment.findByUserAndTypeSync(target, ImmutableSet.of(type))) {
if (punishment.isActive()) { if (punishment.isActive()) {
ErrorUtils.respondGeneric(ctx, "A punishment by " + User.findByIdSync(punishment.getAddedBy()).getLastUsername() + " already covers this user."); ErrorUtils.respondGeneric(ctx, 200, "A punishment by " + User.findByIdSync(punishment.getAddedBy()).getLastUsername() + " already covers this user.");
return; return;
} }
} }
@ -62,11 +62,11 @@ public final class POSTUserPunish implements Handler<RoutingContext> {
return; return;
} }
// We purposely don't do a null check, grants don't have to have a source. // We purposely don't do a null check, punishments don't have to have a source.
User addedBy = User.findByIdSync(ctx.request().getParam("addedBy")); User addedBy = User.findByIdSync(ctx.request().getParam("addedBy"));
if (target.hasPermissionAnywhere(Permissions.PROTECTED_PUNISHMENT)) { if (target.hasPermissionAnywhere(Permissions.PROTECTED_PUNISHMENT)) {
ErrorUtils.respondGeneric(ctx, target.getLastSeenOn() + " is protected from punishments."); ErrorUtils.respondGeneric(ctx, 200, target.getLastSeenOn() + " is protected from punishments.");
return; return;
} }

View File

@ -6,14 +6,16 @@ import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Server; import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup; import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IPUtils; import net.frozenorb.apiv3.utils.IpUtils;
import java.math.BigInteger;
import java.util.Random;
public final class POSTServer implements Handler<RoutingContext> { public final class POSTServer implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) { public void handle(RoutingContext ctx) {
String id = ctx.request().getParam("id"); String id = ctx.request().getParam("id");
String displayName = ctx.request().getParam("displayName"); String displayName = ctx.request().getParam("displayName");
String apiKey = ctx.request().getParam("apiKey");
ServerGroup group = ServerGroup.findById(ctx.request().getParam("group")); ServerGroup group = ServerGroup.findById(ctx.request().getParam("group"));
String ip = ctx.request().getParam("ip"); String ip = ctx.request().getParam("ip");
@ -22,12 +24,13 @@ public final class POSTServer implements Handler<RoutingContext> {
return; return;
} }
if (!IPUtils.isValidIP(ip)) { if (!IpUtils.isValidIp(ip)) {
ErrorUtils.respondInvalidInput(ctx, "IP address \"" + ip + "\" is not valid."); ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + ip + "\" is not valid.");
return; return;
} }
Server server = new Server(id, displayName, apiKey, group, ip); String generatedApiKey = new BigInteger(130, new Random()).toString(32);
Server server = new Server(id, displayName, generatedApiKey, group, ip);
server.insert(); server.insert();
APIv3.respondJson(ctx, server); APIv3.respondJson(ctx, server);
} }

View File

@ -1,6 +1,5 @@
package net.frozenorb.apiv3.routes.servers; package net.frozenorb.apiv3.routes.servers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import io.vertx.core.CompositeFuture; import io.vertx.core.CompositeFuture;
import io.vertx.core.Future; import io.vertx.core.Future;
@ -35,32 +34,35 @@ public final class POSTServerHeartbeat implements Handler<RoutingContext> {
Map<UUID, String> playerNames = extractPlayerNames(reqJson); Map<UUID, String> playerNames = extractPlayerNames(reqJson);
CompositeFuture.all( CompositeFuture.all(
createInfoResponse(actorServer, reqJson.getDouble("tps"), playerNames), createInfoResponse(actorServer, reqJson.getDouble("lastTps"), playerNames),
createPlayerResponse(actorServer, playerNames), createPlayerResponse(actorServer, playerNames),
createPermissionsResponse(actorServerGroup), createPermissionsResponse(actorServerGroup),
createEventsResponse((List<Object>) reqJson.get("events")) createEventsResponse((List<Object>) reqJson.get("events"))
).setHandler((result) -> { ).setHandler((result) -> {
if (result.succeeded()) { if (result.succeeded()) {
// We don't do anything with the info callback, as
// it's just to update our database.
APIv3.respondJson(ctx, ImmutableMap.of( APIv3.respondJson(ctx, ImmutableMap.of(
"info", result.result().result(0),
"players", result.result().result(1), "players", result.result().result(1),
"permissions", result.result().result(2), "permissions", result.result().result(2),
"events", result.result().result(3) "events", result.result().result(3)
)); ));
} else { } else {
ErrorUtils.respondGeneric(ctx, result.cause().getMessage()); ErrorUtils.respondInternalError(ctx, result.cause());
} }
}); });
} }
public Future<Map<String, Object>> createInfoResponse(Server server, double tps, Map<UUID, String> playerNames) { // TODO: ASYNC (MAKE ALL SAVES/INSERTS/ETC USED HERE ASYNC
Future<Map<String, Object>> callback = Future.future(); public Future<Void> createInfoResponse(Server server, double tps, Map<UUID, String> playerNames) {
Future<Void> callback = Future.future();
server.setPlayers(playerNames.keySet()); server.setPlayers(playerNames.keySet());
server.setLastTps(tps); server.setLastTps(tps);
server.setLastUpdatedAt(new Date()); server.setLastUpdatedAt(new Date());
server.save(); server.save();
callback.complete();
return callback; return callback;
} }

View File

@ -5,7 +5,7 @@ import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Grant; import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.IPLogEntry; import net.frozenorb.apiv3.models.IpLogEntry;
import net.frozenorb.apiv3.models.Punishment; import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
@ -24,7 +24,7 @@ public final class GETUserDetails implements Handler<RoutingContext> {
APIv3.respondJson(ctx, ImmutableMap.builder() APIv3.respondJson(ctx, ImmutableMap.builder()
.put("user", user) .put("user", user)
.put("grants", Grant.findByUserSync(user)) .put("grants", Grant.findByUserSync(user))
.put("ipLog", IPLogEntry.findByUserSync(user)) .put("ipLog", IpLogEntry.findByUserSync(user))
.put("punishments", Punishment.findByUserSync(user)) .put("punishments", Punishment.findByUserSync(user))
.put("aliases", user.getAliases()) .put("aliases", user.getAliases())
.put("totpSetup", user.getTotpSecret() != null) .put("totpSetup", user.getTotpSecret() != null)

View File

@ -2,13 +2,12 @@ package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.core.cli.converters.BooleanConverter;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback; import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IPUtils; import net.frozenorb.apiv3.utils.IpUtils;
import net.frozenorb.apiv3.utils.TOTPUtils; import net.frozenorb.apiv3.utils.TOTPUtils;
public final class GETUserRequiresTOTP implements Handler<RoutingContext> { public final class GETUserRequiresTOTP implements Handler<RoutingContext> {
@ -31,8 +30,8 @@ public final class GETUserRequiresTOTP implements Handler<RoutingContext> {
String userIp = ctx.request().getParam("userIp"); String userIp = ctx.request().getParam("userIp");
if (!IPUtils.isValidIP(userIp)) { if (!IpUtils.isValidIp(userIp)) {
ErrorUtils.respondInvalidInput(ctx, "IP address \"" + userIp + "\" is not valid."); ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + userIp + "\" is not valid.");
return; return;
} }
@ -42,7 +41,7 @@ public final class GETUserRequiresTOTP implements Handler<RoutingContext> {
if (preAuthorizedCallback.get()) { if (preAuthorizedCallback.get()) {
APIv3.respondJson(ctx, ImmutableMap.of( APIv3.respondJson(ctx, ImmutableMap.of(
"required", false, "required", false,
"message", "User's IP has already been validated" "message", "User's ip has already been validated"
)); ));
} else { } else {
APIv3.respondJson(ctx, ImmutableMap.of( APIv3.respondJson(ctx, ImmutableMap.of(

View File

@ -32,22 +32,22 @@ public final class POSTUserConfirmRegister implements Handler<RoutingContext> {
// We can't check email != null as that's set while we're pending // We can't check email != null as that's set while we're pending
// confirmation, we have to check the token. // confirmation, we have to check the token.
if (user.getEmailToken() == null) { if (user.getEmailToken() == null) {
ErrorUtils.respondGeneric(ctx, "User provided already has email set."); ErrorUtils.respondGeneric(ctx, 400, "User provided already has email set.");
return; return;
} }
if ((System.currentTimeMillis() - user.getEmailTokenSetAt().getTime()) > TimeUnit.DAYS.toMillis(2)) { if ((System.currentTimeMillis() - user.getEmailTokenSetAt().getTime()) > TimeUnit.DAYS.toMillis(2)) {
ErrorUtils.respondGeneric(ctx, "Email token is expired"); ErrorUtils.respondGeneric(ctx, 200, "Email token is expired");
return; return;
} }
String password = ctx.request().getParam("password"); String password = ctx.request().getParam("password");
if (password.length() < 8) { if (password.length() < 8) {
ErrorUtils.respondGeneric(ctx, "Your password is too short."); ErrorUtils.respondGeneric(ctx, 200, "Your password is too short.");
return; return;
} else if (commonPasswords.contains(password)) { } else if (commonPasswords.contains(password)) {
ErrorUtils.respondGeneric(ctx, "Your password is too common. Please use a more secure password."); ErrorUtils.respondGeneric(ctx, 200, "Your password is too common. Please use a more secure password.");
return; return;
} }

View File

@ -10,16 +10,22 @@ public class POSTUserLeave implements Handler<RoutingContext> {
@Override @Override
public void handle(RoutingContext ctx) { public void handle(RoutingContext ctx) {
User user = User.findByIdSync(ctx.request().getParam("id")); User.findById(ctx.request().getParam("id"), ((user, error) -> {
if (error != null) {
if (user == null) { ErrorUtils.respondInternalError(ctx, error);
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id")); } else if (user == null) {
return; ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
} } else {
user.leftServer();
user.leftServer(); user.save((ignored, error2) -> {
user.save(); if (error2 != null) {
APIv3.respondJson(ctx, user); ErrorUtils.respondInternalError(ctx, error2);
} else {
APIv3.respondJson(ctx, user);
}
});
}
}));
} }
} }

View File

@ -5,11 +5,12 @@ import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor; import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType; import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.models.IPLogEntry; import net.frozenorb.apiv3.actors.ServerActor;
import net.frozenorb.apiv3.models.IpLogEntry;
import net.frozenorb.apiv3.models.Server; import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IPUtils; import net.frozenorb.apiv3.utils.IpUtils;
import net.frozenorb.apiv3.utils.UUIDUtils; import net.frozenorb.apiv3.utils.UUIDUtils;
import java.util.UUID; import java.util.UUID;
@ -24,9 +25,6 @@ public final class POSTUserLogin implements Handler<RoutingContext> {
return; return;
} }
User user = User.findByIdSync(uuid);
String username = ctx.request().getParam("username");
String userIp = ctx.request().getParam("userIp");
Actor actor = ctx.get("actor"); Actor actor = ctx.get("actor");
if (actor.getType() != ActorType.SERVER) { if (actor.getType() != ActorType.SERVER) {
@ -34,33 +32,53 @@ public final class POSTUserLogin implements Handler<RoutingContext> {
return; return;
} }
if (!IPUtils.isValidIP(userIp)) { String username = ctx.request().getParam("username");
ErrorUtils.respondInvalidInput(ctx, "IP address \"" + userIp + "\" is not valid."); String userIp = ctx.request().getParam("userIp");
if (!IpUtils.isValidIp(userIp)) {
ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + userIp + "\" is not valid.");
return; return;
} }
if (user == null) { User.findById(uuid, (user, error) -> {
// Will be saved in the User constructor if (error != null) {
user = new User(uuid, username); ErrorUtils.respondInternalError(ctx, error);
} return;
} else if (user == null) {
user = new User(uuid, username);
user.insert(); // TODO
}
Server actorServer = Server.findById(actor.getName()); User finalUser = user;
IPLogEntry ipLogEntry = IPLogEntry.findByUserAndIpSync(user, userIp); IpLogEntry.findByUserAndIp(user, userIp, (ipLogEntry, error2) -> {
if (error2 != null) {
ErrorUtils.respondInternalError(ctx, error2);
return;
} else if (ipLogEntry == null) {
ipLogEntry = new IpLogEntry(finalUser, userIp);
ipLogEntry.used();
ipLogEntry.insert(); //TODO
} else {
ipLogEntry.used();
ipLogEntry.save(); // TODO
}
// We use a little bit more verbose code here to save on the finalUser.updateUsername(username, (ignored, error3) -> {
// overhead of a .insert() immediately followed by a .save() if (error3 != null) {
if (ipLogEntry == null) { ErrorUtils.respondInternalError(ctx, error3);
ipLogEntry = new IPLogEntry(user, userIp); } else {
ipLogEntry.used(); finalUser.getLoginInfo(((ServerActor) actor).getServer(), (loginInfo, error4) -> {
ipLogEntry.insert(); if (error4 != null) {
} else { ErrorUtils.respondInternalError(ctx, error4);
ipLogEntry.used(); } else {
ipLogEntry.save(); APIv3.respondJson(ctx, loginInfo);
} }
});
user.updateUsername(username); }
APIv3.respondJson(ctx, user.getLoginInfo(actorServer)); });
});
});
} }
} }

View File

@ -6,7 +6,6 @@ import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.NotificationTemplate; import net.frozenorb.apiv3.models.NotificationTemplate;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.unsorted.Notification; import net.frozenorb.apiv3.unsorted.Notification;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
@ -24,7 +23,7 @@ public final class POSTUserNotify implements Handler<RoutingContext> {
} }
if (user.getEmail() == null) { if (user.getEmail() == null) {
ErrorUtils.respondGeneric(ctx, "User provided does not have email set."); ErrorUtils.respondInvalidInput(ctx, "User provided does not have email set.");
return; return;
} }
@ -47,20 +46,17 @@ public final class POSTUserNotify implements Handler<RoutingContext> {
bodyReplacements.put(key, values[0]); bodyReplacements.put(key, values[0]);
});*/ });*/
try { Notification notification = new Notification(template, subjectReplacements, bodyReplacements);
Notification notification = new Notification(template, subjectReplacements, bodyReplacements);
BlockingCallback<Void> callback = new BlockingCallback<>(); notification.sendAsEmail(user.getEmail(), (ignored, error) -> {
notification.sendAsEmail(user.getEmail(), callback); if (error != null) {
callback.get(); ErrorUtils.respondInternalError(ctx, error);
} else {
APIv3.respondJson(ctx, ImmutableMap.of( APIv3.respondJson(ctx, ImmutableMap.of(
"success", true "success", true
)); ));
} catch (Exception ex) { }
ex.printStackTrace(); });
ErrorUtils.respondGeneric(ctx, "Failed to send notification");
}
} }
} }

View File

@ -6,7 +6,6 @@ import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.NotificationTemplate; import net.frozenorb.apiv3.models.NotificationTemplate;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.unsorted.Notification; import net.frozenorb.apiv3.unsorted.Notification;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
@ -40,12 +39,12 @@ public final class POSTUserRegister implements Handler<RoutingContext> {
String email = ctx.request().getParam("email"); String email = ctx.request().getParam("email");
if (!VALID_EMAIL_PATTERN.matcher(email).matches()) { if (!VALID_EMAIL_PATTERN.matcher(email).matches()) {
ErrorUtils.respondGeneric(ctx, email + " is not a valid email."); ErrorUtils.respondInvalidInput(ctx, email + " is not a valid email.");
return; return;
} }
if (user.getEmailToken() != null && (System.currentTimeMillis() - user.getEmailTokenSetAt().getTime()) < TimeUnit.DAYS.toMillis(2)) { if (user.getEmailToken() != null && (System.currentTimeMillis() - user.getEmailTokenSetAt().getTime()) < TimeUnit.DAYS.toMillis(2)) {
ErrorUtils.respondGeneric(ctx, "We just recently sent you a confirmation email. Please wait before trying to register again."); ErrorUtils.respondGeneric(ctx, 200, "We just recently sent you a confirmation email. Please wait before trying to register again.");
return; return;
} }
@ -62,17 +61,15 @@ public final class POSTUserRegister implements Handler<RoutingContext> {
Notification notification = new Notification(NotificationTemplate.findByIdSync("email-confirmation"), replacements, replacements); Notification notification = new Notification(NotificationTemplate.findByIdSync("email-confirmation"), replacements, replacements);
try { notification.sendAsEmail(user.getEmail(), (ignored, error) -> {
BlockingCallback<Void> callback = new BlockingCallback<>(); if (error != null) {
notification.sendAsEmail(user.getEmail(), callback); ErrorUtils.respondInternalError(ctx, error);
callback.get(); } else {
APIv3.respondJson(ctx, ImmutableMap.of( APIv3.respondJson(ctx, ImmutableMap.of(
"success", true "success", true
)); ));
} catch (Exception ex) { }
ex.printStackTrace(); });
ErrorUtils.respondGeneric(ctx, "Failed to send confirmation email. Please contact a MineHQ staff member.");
}
} }
} }

View File

@ -24,7 +24,7 @@ public final class POSTUserSetupTOTP implements Handler<RoutingContext> {
return; return;
} }
GoogleAuthenticatorKey generated = TOTPUtils.generateTOTPKey(); GoogleAuthenticatorKey generated = TOTPUtils.generateTOTPSecret();
user.setTotpSecret(generated.getKey()); user.setTotpSecret(generated.getKey());
user.save(); user.save();

View File

@ -2,14 +2,12 @@ package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler; import io.vertx.core.Handler;
import io.vertx.core.cli.converters.BooleanConverter;
import io.vertx.ext.web.RoutingContext; import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback; import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IPUtils; import net.frozenorb.apiv3.utils.IpUtils;
import net.frozenorb.apiv3.utils.TOTPUtils; import net.frozenorb.apiv3.utils.TOTPUtils;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -31,8 +29,8 @@ public final class POSTUserVerifyTOTP implements Handler<RoutingContext> {
String userIp = ctx.request().getParam("userIp"); String userIp = ctx.request().getParam("userIp");
if (!IPUtils.isValidIP(userIp)) { if (!IpUtils.isValidIp(userIp)) {
ErrorUtils.respondInvalidInput(ctx, "IP address \"" + userIp + "\" is not valid."); ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + userIp + "\" is not valid.");
return; return;
} }

View File

@ -26,18 +26,18 @@ public final class PUTUserMeta implements Handler<RoutingContext> {
return; return;
} }
Document data = Document.parse(ctx.getBodyAsString()); Document reqJson = Document.parse(ctx.getBodyAsString());
UserMetaEntry metaEntry = UserMetaEntry.findByUserAndGroupSync(user, serverGroup); UserMetaEntry metaEntry = UserMetaEntry.findByUserAndGroupSync(user, serverGroup);
if (metaEntry == null) { if (metaEntry == null) {
metaEntry = new UserMetaEntry(user, serverGroup, data); metaEntry = new UserMetaEntry(user, serverGroup, reqJson);
metaEntry.insert(); metaEntry.insert();
} else { } else {
metaEntry.setData(data); metaEntry.setData(reqJson);
metaEntry.save(); metaEntry.save();
} }
APIv3.respondJson(ctx, data); APIv3.respondJson(ctx, reqJson);
} }
} }

View File

@ -1,40 +0,0 @@
package net.frozenorb.apiv3.serialization;
import org.bson.BsonReader;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import java.util.UUID;
public final class MineHQCodecProvider implements CodecProvider {
public <T> Codec<T> get(Class<T> clazz, CodecRegistry codecRegistry) {
if (clazz == UUID.class) {
return (Codec<T>) new Codec<UUID>() {
@Override
public UUID decode(BsonReader bsonReader, DecoderContext decoderContext) {
return UUID.fromString(bsonReader.readString());
}
@Override
public void encode(BsonWriter bsonWriter, UUID uuid, EncoderContext encoderContext) {
bsonWriter.writeString(uuid == null ? null : uuid.toString());
}
@Override
public Class<UUID> getEncoderClass() {
return UUID.class;
}
};
} else {
return null;
}
}
}

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.serialization; package net.frozenorb.apiv3.serialization.gson;
import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonReader;

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.serialization; package net.frozenorb.apiv3.serialization.gson;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.serialization; package net.frozenorb.apiv3.serialization.gson;
import com.google.gson.ExclusionStrategy; import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes; import com.google.gson.FieldAttributes;

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.serialization; package net.frozenorb.apiv3.serialization.jackson;
import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.serialization; package net.frozenorb.apiv3.serialization.jackson;
import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;

View File

@ -0,0 +1,28 @@
package net.frozenorb.apiv3.serialization.mongodb;
import org.bson.BsonReader;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import java.util.UUID;
public final class UUIDCodec implements Codec<UUID> {
@Override
public UUID decode(BsonReader bsonReader, DecoderContext decoderContext) {
return UUID.fromString(bsonReader.readString());
}
@Override
public void encode(BsonWriter bsonWriter, UUID uuid, EncoderContext encoderContext) {
bsonWriter.writeString(uuid == null ? null : uuid.toString());
}
@Override
public Class<UUID> getEncoderClass() {
return UUID.class;
}
}

View File

@ -0,0 +1,19 @@
package net.frozenorb.apiv3.serialization.mongodb;
import org.bson.codecs.Codec;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistry;
import java.util.UUID;
public final class UUIDCodecProvider implements CodecProvider {
public <T> Codec<T> get(Class<T> clazz, CodecRegistry codecRegistry) {
if (clazz == UUID.class) {
return (Codec<T>) new UUIDCodec();
} else {
return null;
}
}
}

View File

@ -1,30 +1,17 @@
package net.frozenorb.apiv3.unsorted; package net.frozenorb.apiv3.unsorted;
import com.cribbstechnologies.clients.mandrill.exception.RequestFailedException; import com.google.common.collect.ImmutableList;
import com.cribbstechnologies.clients.mandrill.model.MandrillHtmlMessage; import com.google.common.net.MediaType;
import com.cribbstechnologies.clients.mandrill.model.MandrillMessageRequest;
import com.cribbstechnologies.clients.mandrill.model.MandrillRecipient;
import com.cribbstechnologies.clients.mandrill.request.MandrillMessagesRequest;
import com.mongodb.async.SingleResultCallback; import com.mongodb.async.SingleResultCallback;
import com.twilio.sdk.TwilioRestException; import io.vertx.core.http.HttpHeaders;
import com.twilio.sdk.resource.factory.MessageFactory;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.NotificationTemplate; import net.frozenorb.apiv3.models.NotificationTemplate;
import net.frozenorb.apiv3.utils.MandrillUtils; import org.bson.Document;
import net.frozenorb.apiv3.utils.TwillioUtils;
import org.apache.http.NameValuePair;
import org.apache.http.message.BasicNameValuePair;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map; import java.util.Map;
public final class Notification { public final class Notification {
private static final MandrillMessagesRequest mandrillMessagesRequest = MandrillUtils.createMessagesRequest();
private static final MessageFactory twillioMessageFactory = TwillioUtils.createMessageFactory();
private final String subject; private final String subject;
private final String body; private final String body;
@ -33,60 +20,34 @@ public final class Notification {
this.body = template.fillBody(bodyReplacements); this.body = template.fillBody(bodyReplacements);
} }
public void sendAsEmail(String email, SingleResultCallback<Void> callback) throws IOException { public void sendAsEmail(String email, SingleResultCallback<Void> callback) {
MandrillHtmlMessage message = new MandrillHtmlMessage(); Document messageJson = new Document();
message.setFrom_email("no-reply@minehq.com"); messageJson.put("html", body);
message.setFrom_name("MineHQ Network"); messageJson.put("subject", subject);
message.setSubject(subject); messageJson.put("from_email", "no-reply@minehq.com");
message.setHtml(body); messageJson.put("from_name", "MineHQ Network");
message.setTo(new MandrillRecipient[] { messageJson.put("to", ImmutableList.of(
new MandrillRecipient(null, email) new Document("email", email).append("name", null).append("type", "to")
}); ));
APIv3.getVertxInstance().executeBlocking((future) -> { Document bodyJson = new Document();
try { bodyJson.put("key", APIv3.getConfig().getProperty("mandrill.apiKey"));
MandrillMessageRequest request = new MandrillMessageRequest(); bodyJson.put("message", messageJson);
request.setMessage(message);
mandrillMessagesRequest.sendMessage(request); APIv3.getHttpClient().post("mandrillapp.com", "/api/1.0/messages/send.json", (response) -> {
APIv3.getStatsD().incrementCounter("apiv3.notification.email.success"); response.bodyHandler((body) -> {
future.succeeded();
} catch (RequestFailedException ex) {
APIv3.getStatsD().incrementCounter("apiv3.notification.email.failure");
future.fail(new IOException("Failed to send notification to user", ex));
}
}, (result) -> {
if (result.succeeded()) {
callback.onResult(null, null); callback.onResult(null, null);
} else { });
callback.onResult(null, result.cause());
} response.exceptionHandler((error) -> {
}); callback.onResult(null, error);
});
}).putHeader(HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString()).end(bodyJson.toJson());
} }
public void sendAsText(String phoneNumber, SingleResultCallback<Void> callback) throws IOException { public void sendAsText(String phoneNumber, SingleResultCallback<Void> callback) {
List<NameValuePair> params = new ArrayList<>(); callback.onResult(null, new UnsupportedOperationException());
params.add(new BasicNameValuePair("To", phoneNumber));
params.add(new BasicNameValuePair("From", "+13108795180"));
params.add(new BasicNameValuePair("Body", body));
APIv3.getVertxInstance().executeBlocking((future) -> {
try {
twillioMessageFactory.create(params);
APIv3.getStatsD().incrementCounter("apiv3.notification.text.success");
future.succeeded();
} catch (TwilioRestException ex) {
APIv3.getStatsD().incrementCounter("apiv3.notification.text.failure");
future.fail(new IOException("Failed to send notification to user", ex));
}
}, (result) -> {
if (result.succeeded()) {
callback.onResult(null, null);
} else {
callback.onResult(null, result.cause());
}
});
} }
} }

View File

@ -9,24 +9,27 @@ import net.frozenorb.apiv3.APIv3;
public class ErrorUtils { public class ErrorUtils {
public static void respondServerOnly(RoutingContext ctx) { public static void respondServerOnly(RoutingContext ctx) {
respondGeneric(ctx, "This action can only be performed when requested by a server."); respondGeneric(ctx, 400, "This action can only be performed when requested by a server.");
} }
public static void respondNotFound(RoutingContext ctx, String itemType, String id) { public static void respondNotFound(RoutingContext ctx, String itemType, String id) {
respondGeneric(ctx, "Not found: " + itemType + " with id " + id + " cannot be found."); respondGeneric(ctx, 404, "Not found: " + itemType + " with id " + id + " cannot be found.");
} }
public static void respondInvalidInput(RoutingContext ctx, String message) { public static void respondInvalidInput(RoutingContext ctx, String message) {
respondGeneric(ctx, "Invalid input: " + message + "."); respondGeneric(ctx, 400, "Invalid input: " + message + ".");
} }
public static void respondRequiredInput(RoutingContext ctx, String field) { public static void respondRequiredInput(RoutingContext ctx, String field) {
respondGeneric(ctx, "Field \"" + field + "\" is required."); respondGeneric(ctx, 400, "Field \"" + field + "\" is required.");
} }
public static void respondGeneric(RoutingContext ctx, String message) { public static void respondInternalError(RoutingContext ctx, Throwable error) {
// TODO: Proper status codes respondGeneric(ctx, 500, "Internal error: " + error.getClass().getSimpleName());
APIv3.respondJson(ctx, 400, ImmutableMap.of( }
public static void respondGeneric(RoutingContext ctx, int code, String message) {
APIv3.respondJson(ctx, code, ImmutableMap.of(
"success", false, "success", false,
"message", message "message", message
)); ));

View File

@ -5,7 +5,7 @@ import lombok.experimental.UtilityClass;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@UtilityClass @UtilityClass
public class IPUtils { public class IpUtils {
private static final Pattern VALID_IP_PATTERN = Pattern.compile( private static final Pattern VALID_IP_PATTERN = Pattern.compile(
"^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "^([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
@ -13,7 +13,7 @@ public class IPUtils {
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." + "([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\." +
"([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"); "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
public static boolean isValidIP(String ip) { public static boolean isValidIp(String ip) {
return ip != null && VALID_IP_PATTERN.matcher(ip).matches(); return ip != null && VALID_IP_PATTERN.matcher(ip).matches();
} }

View File

@ -1,32 +0,0 @@
package net.frozenorb.apiv3.utils;
import com.cribbstechnologies.clients.mandrill.request.MandrillMessagesRequest;
import com.cribbstechnologies.clients.mandrill.request.MandrillRESTRequest;
import com.cribbstechnologies.clients.mandrill.util.MandrillConfiguration;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import org.apache.http.impl.client.HttpClientBuilder;
@UtilityClass
public class MandrillUtils {
public static MandrillMessagesRequest createMessagesRequest() {
MandrillConfiguration config = new MandrillConfiguration();
config.setApiKey(APIv3.getConfig().getProperty("mandrill.apiKey"));
config.setApiVersion("1.0");
config.setBaseURL("https://mandrillapp.com/api");
MandrillRESTRequest request = new MandrillRESTRequest();
request.setConfig(config);
request.setObjectMapper(new ObjectMapper());
request.setHttpClient(HttpClientBuilder.create().build());
MandrillMessagesRequest messagesRequest = new MandrillMessagesRequest();
messagesRequest.setRequest(request);
return messagesRequest;
}
}

View File

@ -3,9 +3,9 @@ package net.frozenorb.apiv3.utils;
import com.mongodb.async.SingleResultCallback; import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import org.bson.Document; import org.bson.Document;
import java.io.IOException;
import java.util.UUID; import java.util.UUID;
@UtilityClass @UtilityClass
@ -18,19 +18,16 @@ public class MojangUtils {
String name = resJson.getString("name"); String name = resJson.getString("name");
if (name == null) { if (name == null) {
APIv3.getStatsD().incrementCounter("apiv3.mojang.sessionServer.rateLimited"); callback.onResult(null, new IOException("Hit Mojang API rate limit: " + resJson.toJson()));
callback.onResult(null, new RuntimeException("Hit Mojang API rate limit: " + resJson.toJson()));
} else { } else {
APIv3.getStatsD().incrementCounter("apiv3.mojang.sessionServer.success");
callback.onResult(name, null); callback.onResult(name, null);
} }
}); });
response.exceptionHandler((error) -> { response.exceptionHandler((error) -> {
APIv3.getStatsD().incrementCounter("apiv3.mojang.sessionServer.failure");
callback.onResult(null, error); callback.onResult(null, error);
}); });
}); }).end();
} }
} }

View File

@ -1,12 +1,9 @@
package net.frozenorb.apiv3.utils; package net.frozenorb.apiv3.utils;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Rank; import net.frozenorb.apiv3.models.Rank;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -66,49 +63,4 @@ public class PermissionUtils {
return result; return result;
} }
public static Map<String, List<String>> fromLegacy(String defaultGroup, String customGroup) {
Map<String, List<String>> defaultPerms = legacyServerGroupToPerms(APIv3.getGson().fromJson(defaultGroup, HashMap.class));
Map<String, List<String>> customPerms = legacyServerGroupToPerms(APIv3.getGson().fromJson(customGroup, HashMap.class));
for (Map.Entry<String, List<String>> customPerm : customPerms.entrySet()) {
List<String> 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<String, List<String>> legacyServerGroupToPerms(Map<String, Object> group) {
Map<String, List<String>> result = new HashMap<>();
Map<String, Object> permissions = (Map<String, Object>) group.get("permissions");
for (Map.Entry<String, Object> entry : permissions.entrySet()) {
List<String> perms = (List<String>) ((Map<String, Object>) 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;
}
} }

View File

@ -5,44 +5,35 @@ import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorConfig; import com.warrenstrange.googleauth.GoogleAuthenticatorConfig;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey; import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator; import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator;
import io.vertx.redis.RedisClient;
import io.vertx.redis.RedisOptions;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
@UtilityClass @UtilityClass
public class TOTPUtils { public class TOTPUtils {
private static GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator(new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder().setWindowSize(10).build()); private static GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator(new GoogleAuthenticatorConfig.GoogleAuthenticatorConfigBuilder().setWindowSize(10).build());
public static GoogleAuthenticatorKey generateTOTPKey() { public static GoogleAuthenticatorKey generateTOTPSecret() {
return googleAuthenticator.createCredentials(); return googleAuthenticator.createCredentials();
} }
public static boolean authorizeUser(User user, int code) { public static boolean authorizeUser(User user, int code) {
boolean authorized = googleAuthenticator.authorize(user.getTotpSecret(), code); return googleAuthenticator.authorize(user.getTotpSecret(), code);
if (!authorized) {
APIv3.getStatsD().incrementCounter("apiv3.totp.failure");
}
return authorized;
} }
public static String getQRCodeURL(User user, GoogleAuthenticatorKey key) { public static String getQRCodeURL(User user, GoogleAuthenticatorKey secret) {
return GoogleAuthenticatorQRGenerator.getOtpAuthURL( return GoogleAuthenticatorQRGenerator.getOtpAuthURL(
"MineHQ Network", "MineHQ Network",
user.getLastUsername(), user.getLastUsername(),
key secret
); );
} }
public static void isPreAuthorized(User user, String ip, SingleResultCallback<Boolean> callback) { public static void isPreAuthorized(User user, String ip, SingleResultCallback<Boolean> callback) {
APIv3.getRedisClient().exists(user.getId() + ":preAuthorizedIP:" + ip.toLowerCase(), (result) -> { APIv3.getRedisClient().exists(user.getId() + ":preAuthorizedIp:" + ip.toLowerCase(), (result) -> {
if (result.succeeded()) { if (result.succeeded()) {
callback.onResult(result.result() == 1 , null); callback.onResult(result.result() == 1 , null);
} else { } else {
@ -52,7 +43,7 @@ public class TOTPUtils {
} }
public static void markPreAuthorized(User user, String ip, long duration, TimeUnit unit, SingleResultCallback<Void> callback) { public static void markPreAuthorized(User user, String ip, long duration, TimeUnit unit, SingleResultCallback<Void> callback) {
String key = user.getId() + ":preAuthorizedIP:" + ip.toLowerCase(); String key = user.getId() + ":preAuthorizedIp:" + ip.toLowerCase();
APIv3.getRedisClient().set(key, "", (result) -> { APIv3.getRedisClient().set(key, "", (result) -> {
if (result.succeeded()) { if (result.succeeded()) {

View File

@ -1,22 +0,0 @@
package net.frozenorb.apiv3.utils;
import com.twilio.sdk.TwilioRestClient;
import com.twilio.sdk.resource.factory.MessageFactory;
import com.twilio.sdk.resource.instance.Account;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
@UtilityClass
public class TwillioUtils {
public static MessageFactory createMessageFactory() {
TwilioRestClient twillioClient = new TwilioRestClient(
APIv3.getConfig().getProperty("twillio.accountSID"),
APIv3.getConfig().getProperty("twillio.authToken")
);
Account account = twillioClient.getAccount();
return account.getMessageFactory();
}
}

View File

@ -1,7 +1,6 @@
package net.frozenorb.apiv3.utils; package net.frozenorb.apiv3.utils;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import java.util.UUID; import java.util.UUID;
@ -9,13 +8,7 @@ import java.util.UUID;
public class UUIDUtils { public class UUIDUtils {
public static boolean isAcceptableUUID(UUID uuid) { public static boolean isAcceptableUUID(UUID uuid) {
boolean acceptable = uuid.version() == 4; return uuid.version() == 4;
if (!acceptable) {
APIv3.getStatsD().incrementCounter("apiv3.uuid.invalid");
}
return acceptable;
} }
} }