Push a bunch of experimental code (this doesn't currently work/compile)

This commit is contained in:
Colin McDonald 2016-06-09 22:39:23 -04:00
parent 7d626afa20
commit 60a622fc43
80 changed files with 1156 additions and 841 deletions

View File

@ -8,9 +8,7 @@ mongo.username=
mongo.password=
redis.address=localhost
redis.port=6379
http.address=0.0.0.0
http.port=80
http.workerThreads=
twillio.accountSID=AC9e2f88c5690134d29a56f698de3cd740
twillio.authToken=982592505a171d3be6b0722f5ecacc0e
mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ

88
pom.xml
View File

@ -58,6 +58,7 @@
</repositories>
<dependencies>
<!-- Vert.x -->
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
@ -68,36 +69,39 @@
<artifactId>vertx-web</artifactId>
<version>LATEST</version>
</dependency>
<!-- Google Libs -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>com.indeed</groupId>
<artifactId>java-dogstatsd-client</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>com.bugsnag</groupId>
<artifactId>bugsnag</artifactId>
<version>2.0.0</version>
</dependency>
<!-- Mongo -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.2.2</version>
<artifactId>mongodb-driver-async</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>eu.dozd</groupId>
<artifactId>mongo-mapper</artifactId>
<version>1.0.1</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<!-- Notifications -->
<dependency>
<groupId>com.cribbstechnologies.clients</groupId>
<artifactId>mandrillClient</artifactId>
@ -108,46 +112,34 @@
<artifactId>twilio-java-sdk</artifactId>
<version>6.3.0</version>
</dependency>
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.3m</version>
</dependency>
<dependency>
<groupId>org.mongodb.morphia</groupId>
<artifactId>morphia</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.mongodb.morphia</groupId>
<artifactId>morphia-logging-slf4j</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4</version>
</dependency>
<!-- TOTP -->
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>0.5.0</version>
</dependency>
<!-- Monitoring -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.2.0</version>
<groupId>com.indeed</groupId>
<artifactId>java-dogstatsd-client</artifactId>
<version>2.0.12</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>1.8.0</version>
<groupId>com.bugsnag</groupId>
<artifactId>bugsnag</artifactId>
<version>2.0.0</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.4</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@ -6,23 +6,36 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mongodb.*;
import com.mongodb.client.MongoDatabase;
import com.mongodb.Block;
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.MongoClients;
import com.mongodb.async.client.MongoDatabase;
import com.mongodb.connection.ClusterSettings;
import com.timgroup.statsd.NonBlockingStatsDClient;
import com.timgroup.statsd.StatsDClient;
import eu.dozd.mongo.MongoMapper;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpServer;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.LoggerHandler;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.filters.*;
import net.frozenorb.apiv3.filters.ActorAttributeFilter;
import net.frozenorb.apiv3.filters.AuthorizationFilter;
import net.frozenorb.apiv3.filters.MetricsHandler;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.IPLogEntry;
import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.routes.GETDump;
import net.frozenorb.apiv3.routes.GETWhoAmI;
import net.frozenorb.apiv3.routes.NotFound;
import net.frozenorb.apiv3.routes.POSTMetrics;
import net.frozenorb.apiv3.routes.announcements.GETAnnouncements;
import net.frozenorb.apiv3.routes.auditLog.GETAuditLog;
@ -48,16 +61,11 @@ import net.frozenorb.apiv3.serialization.DateTypeAdapter;
import net.frozenorb.apiv3.serialization.FollowAnnotationExclusionStrategy;
import net.frozenorb.apiv3.serialization.ObjectIdTypeAdapter;
import net.frozenorb.apiv3.unsorted.BugsnagSLF4JLogger;
import net.frozenorb.apiv3.unsorted.LoggingExceptionHandler;
import net.frozenorb.apiv3.utils.IPUtils;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.Document;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
import org.mongodb.morphia.converters.UUIDConverter;
import org.mongodb.morphia.logging.MorphiaLoggerFactory;
import org.mongodb.morphia.logging.slf4j.SLF4JLoggerImplFactory;
import redis.clients.jedis.JedisPool;
import java.io.FileInputStream;
@ -66,13 +74,11 @@ import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static spark.Spark.*;
import static spark.route.RouteOverview.enableRouteOverview;
@Slf4j
public final class APIv3 {
public final class APIv3 extends AbstractVerticle {
@Getter private static Datastore datastore;
@Getter private static HttpClient httpClient;
@Getter private static MongoDatabase database;
@Getter private static Properties config = new Properties();
@Getter private static JedisPool redisPool;
@Getter private static StatsDClient statsD;
@ -82,7 +88,8 @@ public final class APIv3 {
.setExclusionStrategies(new FollowAnnotationExclusionStrategy())
.create();
APIv3() {
@Override
public void start() {
setupConfig();
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", config.getProperty("logging.level"));
@ -90,10 +97,9 @@ public final class APIv3 {
setupRedis();
setupMetrics();
setupBugsnag();
setupHttp();
setupHttpServer();
//convertData("158.69.126.126", true);
LoggingFilter.setDebug(Boolean.valueOf(config.getProperty("logging.debug")));
}
private void setupConfig() {
@ -108,28 +114,33 @@ public final class APIv3 {
ImmutableList<MongoCredential> credentials = ImmutableList.of();
if (!config.getProperty("mongo.username").isEmpty()) {
credentials = ImmutableList.of(MongoCredential.createCredential(
config.getProperty("mongo.username"),
config.getProperty("mongo.database"),
config.getProperty("mongo.password").toCharArray()
));
credentials = ImmutableList.of(
MongoCredential.createCredential(
config.getProperty("mongo.username"),
config.getProperty("mongo.database"),
config.getProperty("mongo.password").toCharArray()
)
);
}
MongoClient mongoClient = new MongoClient(new ServerAddress(
config.getProperty("mongo.address"),
Integer.parseInt(config.getProperty("mongo.port"))),
credentials
);
ClusterSettings clusterSettings = ClusterSettings
.builder()
.hosts(ImmutableList.of(
new ServerAddress(
config.getProperty("mongo.address"),
Integer.parseInt(config.getProperty("mongo.port"))
)
))
.build();
MongoClientSettings settings = MongoClientSettings
.builder()
.codecRegistry(CodecRegistries.fromProviders(MongoMapper.getProviders()))
.credentialList(credentials)
.clusterSettings(clusterSettings).build();
MorphiaLoggerFactory.reset();
MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class);
Morphia morphia = new Morphia();
morphia.mapPackage("net.frozenorb.apiv3.models");
morphia.getMapper().getConverters().addConverter(new UUIDConverter());
datastore = morphia.createDatastore(mongoClient, config.getProperty("mongo.database"));
datastore.ensureIndexes();
MongoClient client = MongoClients.create(settings);
database = client.getDatabase(config.getProperty("mongo.database"));
// TODO: Indexes
}
private void setupRedis() {
@ -160,103 +171,105 @@ public final class APIv3 {
bugsnag.setLogger(new BugsnagSLF4JLogger());
}
private void setupHttp() {
Router.router()
ipAddress(config.getProperty("http.address"));
port(Integer.parseInt(config.getProperty("http.port")));
String workerThreads = config.getProperty("http.workerThreads");
private void setupHttpServer() {
HttpServer webServer = vertx.createHttpServer();
Router mainRouter = Router.router(vertx);
if (!workerThreads.isEmpty()) {
threadPool(Integer.parseInt(workerThreads));
}
before(new MetricsBeforeFilter());
before(new ContentTypeFilter());
before(new ActorAttributeFilter());
before(new AuthorizationFilter());
after(new MetricsAfterFilter());
after(new LoggingFilter());
exception(Exception.class, new LoggingExceptionHandler());
mainRouter.route().handler(new MetricsHandler());
mainRouter.route().handler(new ActorAttributeFilter());
mainRouter.route().handler(new AuthorizationFilter());
mainRouter.route().handler(LoggerHandler.create());
mainRouter.route().handler(BodyHandler.create());
// TODO: The commented out routes
post("/metrics", new POSTMetrics(), gson::toJson);
get("/announcements", new GETAnnouncements(), gson::toJson);
get("/auditLog", new GETAuditLog(), gson::toJson);
get("/chatFilterList", new GETChatFilterList(), gson::toJson);
get("/dump/:type", new GETDump(), gson::toJson);
get("/whoami", new GETWhoAmI(), gson::toJson);
enableRouteOverview("/routes");
mainRouter.post("/metrics").blockingHandler(new POSTMetrics());
mainRouter.get("/announcements").blockingHandler(new GETAnnouncements());
mainRouter.get("/auditLog").blockingHandler(new GETAuditLog());
mainRouter.get("/chatFilterList").blockingHandler(new GETChatFilterList());
mainRouter.get("/dump/:type").blockingHandler(new GETDump());
mainRouter.get("/whoami").blockingHandler(new GETWhoAmI());
get("/grant/:id", new GETGrant(), gson::toJson);
get("/grants", new GETGrants(), gson::toJson);
delete("/grant/:id", new DELETEGrant(), gson::toJson);
mainRouter.get("/grant/:id").blockingHandler(new GETGrant());
mainRouter.get("/grants").blockingHandler(new GETGrants());
mainRouter.delete("/grant/:id").blockingHandler(new DELETEGrant());
get("/notificationTemplate/:id", new GETNotificationTemplate(), gson::toJson);
get("/notificationTemplates", new GETNotificationTemplates(), gson::toJson);
post("/notificationTemplate", new POSTNotificationTemplate(), gson::toJson);
//put("/notificationTemplate/:id", new PUTNotificationTemplate(), gson::toJson);
delete("/notificationTemplate/:id", new DELETENotificationTemplate(), gson::toJson);
mainRouter.get("/notificationTemplate/:id").blockingHandler(new GETNotificationTemplate());
mainRouter.get("/notificationTemplates").blockingHandler(new GETNotificationTemplates());
mainRouter.post("/notificationTemplate").blockingHandler(new POSTNotificationTemplate());
//mainRouter.put("/notificationTemplate/:id").blockingHandler(new PUTNotificationTemplate());
mainRouter.delete("/notificationTemplate/:id").blockingHandler(new DELETENotificationTemplate());
get("/punishment/:id", new GETPunishment(), gson::toJson);
get("/punishments", new GETPunishments(), gson::toJson);
delete("/punishment/:id", new DELETEPunishment(), gson::toJson);
mainRouter.get("/punishment/:id").blockingHandler(new GETPunishment());
mainRouter.get("/punishments").blockingHandler(new GETPunishments());
mainRouter.delete("/punishment/:id").blockingHandler(new DELETEPunishment());
get("/rank/:id", new GETRank(), gson::toJson);
get("/ranks", new GETRanks(), gson::toJson);
post("/rank", new POSTRank(), gson::toJson);
//put("/rank/:id", new PUTRank(), gson::toJson);
delete("/rank/:id", new DELETERank(), gson::toJson);
mainRouter.get("/rank/:id").blockingHandler(new GETRank());
mainRouter.get("/ranks").blockingHandler(new GETRanks());
mainRouter.post("/rank").blockingHandler(new POSTRank());
//put("/rank/:id").blockingHandler(new PUTRank());
mainRouter.delete("/rank/:id").blockingHandler(new DELETERank());
get("/serverGroup/:id", new GETServerGroup(), gson::toJson);
get("/serverGroups", new GETServerGroups(), gson::toJson);
post("/serverGroup", new POSTServerGroup(), gson::toJson);
//put("/serverGroup/:id", new PUTServerGroup(), gson::toJson);
delete("/serverGroup/:id", new DELETEServerGroup(), gson::toJson);
mainRouter.get("/serverGroup/:id").blockingHandler(new GETServerGroup());
mainRouter.get("/serverGroups").blockingHandler(new GETServerGroups());
mainRouter.post("/serverGroup").blockingHandler(new POSTServerGroup());
//put("/serverGroup/:id").blockingHandler(new PUTServerGroup());
mainRouter.delete("/serverGroup/:id").blockingHandler(new DELETEServerGroup());
get("/server/:id", new GETServer(), gson::toJson);
get("/servers", new GETServers(), gson::toJson);
post("/server/heartbeat", new POSTServerHeartbeat(), gson::toJson);
post("/server", new POSTServer(), gson::toJson);
//put("/server/:id", new PUTServer(), gson::toJson);
delete("/server/:id", new DELETEServer(), gson::toJson);
mainRouter.get("/server/:id").blockingHandler(new GETServer());
mainRouter.get("/servers").blockingHandler(new GETServers());
mainRouter.post("/server/heartbeat").blockingHandler(new POSTServerHeartbeat());
mainRouter.post("/server").blockingHandler(new POSTServer());
//put("/server/:id").blockingHandler(new PUTServer());
mainRouter.delete("/server/:id").blockingHandler(new DELETEServer());
get("/staff", new GETStaff(), gson::toJson);
get("/user/:id/details", new GETUserDetails(), gson::toJson);
get("/user/:id/meta/:serverGroup", new GETUserMeta(), gson::toJson);
get("/user/:id/grants", new GETUserGrants(), gson::toJson);
get("/user/:id/punishments", new GETUserPunishments(), gson::toJson);
get("/user/:id/ipLog", new GETUserIPLog(), gson::toJson);
get("/user/:id/requiresTOTP", new GETUserRequiresTOTP(), gson::toJson);
get("/user/:id/verifyPassword", new GETUserVerifyPassword(), gson::toJson);
get("/user/:id", new GETUser(), gson::toJson);
post("/user/:id/verifyTOTP", new POSTUserVerifyTOTP(), gson::toJson);
post("/user/:id/grant", new POSTUserGrant(), gson::toJson);
post("/user/:id/punish", new POSTUserPunish(), gson::toJson);
post("/user/:id/login", new POSTUserLogin(), gson::toJson);
post("/user/:id/leave", new POSTUserLeave(), gson::toJson);
post("/user/:id/notify", new POSTUserNotify(), gson::toJson);
post("/user/:id/register", new POSTUserRegister(), gson::toJson);
post("/user/:id/setupTOTP", new POSTUserSetupTOTP(), gson::toJson);
post("/user/confirmRegister/:emailToken", new POSTUserConfirmRegister(), gson::toJson);
put("/user/:id/meta/:serverGroup", new PUTUserMeta(), gson::toJson);
delete("/user/:id/meta/:serverGroup", new DELETEUserMeta(), gson::toJson);
delete("/user/:id/punishment", new DELETEUserPunishment(), gson::toJson);
mainRouter.get("/staff").blockingHandler(new GETStaff());
mainRouter.get("/user/:id/details").blockingHandler(new GETUserDetails());
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/verifyPassword").blockingHandler(new GETUserVerifyPassword());
mainRouter.get("/user/:id").blockingHandler(new GETUser());
mainRouter.post("/user/:id/verifyTOTP").blockingHandler(new POSTUserVerifyTOTP());
mainRouter.post("/user/:id/grant").blockingHandler(new POSTUserGrant());
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/register").blockingHandler(new POSTUserRegister());
mainRouter.post("/user/:id/setupTOTP").blockingHandler(new POSTUserSetupTOTP());
mainRouter.post("/user/confirmRegister/:emailToken").blockingHandler(new POSTUserConfirmRegister());
mainRouter.put("/user/:id/meta/:serverGroup").blockingHandler(new PUTUserMeta());
mainRouter.delete("/user/:id/meta/:serverGroup").blockingHandler(new DELETEUserMeta());
mainRouter.delete("/user/:id/punishment").blockingHandler(new DELETEUserPunishment());
// There's no way to do a JSON 404 page w/o doing this :(
get("/*", new NotFound(), gson::toJson);
post("/*", new NotFound(), gson::toJson);
put("/*", new NotFound(), gson::toJson);
delete("/*", new NotFound(), gson::toJson);
mainRouter.getRoutes().forEach((route) -> {
System.out.println(route.getClass() + "||" + route.getPath());
});
int port = Integer.parseInt(config.getProperty("http.port"));
webServer.requestHandler(mainRouter::accept).listen(port);
}
public static void respond(RoutingContext ctx, Object response) {
private void setupHttpClient() {
httpClient = vertx.createHttpClient();
}
public static void respondJson(RoutingContext ctx, Object response) {
respondJson(ctx, 200, response);
}
public static void respondJson(RoutingContext ctx, int code, Object response) {
ctx.response().putHeader("Content-Type", "application/json");
ctx.response().setStatusCode(code);
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 = new MongoClient(oldIp).getDatabase("minehq");
MongoDatabase importFrom = MongoClients.create(oldIp).getDatabase("minehq");
Map<ObjectId, UUID> mongoIdToUUID = new HashMap<>();
AtomicInteger skippedUsers = new AtomicInteger();
AtomicInteger skippedPunishments = new AtomicInteger();
@ -299,12 +312,12 @@ public final class APIv3 {
);
if (forReal) {
APIv3.getDatastore().save(created);
created.insert();
}
log.info("Created user " + created.getLastUsername() + " (" + created.getId() + ")");
}
});
}, (a, b) -> {});
importFrom.getCollection("punishment").find().forEach(new Block<Document>() {
@ -322,6 +335,8 @@ public final class APIv3 {
return;
}
com.mongodb.
Punishment created = new Punishment(
new ObjectId().toString(),
target,
@ -339,12 +354,12 @@ public final class APIv3 {
);
if (forReal) {
APIv3.getDatastore().save(created);
created.insert();
}
log.info("Created punishment " + created.getId() + " (" + created.getType() + ")");
}
});
}, (a, b) -> {});
importFrom.getCollection("grant").find().forEach(new Block<Document>() {
@ -382,12 +397,12 @@ public final class APIv3 {
);
if (forReal) {
APIv3.getDatastore().save(created);
created.insert();
}
log.info("Created grant " + created.getId() + " (" + created.getRank() + ")");
}
});
}, (a, b) -> {});
importFrom.getCollection("iplog").find().forEach(new Block<Document>() {
@ -423,12 +438,12 @@ public final class APIv3 {
);
if (forReal) {
APIv3.getDatastore().save(created);
created.insert();
}
log.info("Created ip log entry " + created.getId() + " (" + created.getUser() + " - " + created.getUserIp() + ")");
}
});
}, (a, b) -> {});
log.info("Skipped " + skippedUsers.get() + " users, " + skippedPunishments.get() + " punishments, " + skippedGrants.get() + " grants, and " + skippedIpLogs.get() + " ip logs");
}

View File

@ -1,9 +1,11 @@
package net.frozenorb.apiv3;
import io.vertx.core.Vertx;
final class Main {
public static void main(String[] args) {
new APIv3();
Vertx.vertx().deployVerticle(new APIv3());
}
}

View File

@ -23,7 +23,7 @@ public final class UserActor implements Actor {
if (cachedAuthorized != null) {
return cachedAuthorized;
} else {
String highestRankId = user.getHighestRank().getId();
String highestRankId = user.getHighestRankAnywhere().getId();
cachedAuthorized = permittedUserRanks.contains(highestRankId.toLowerCase());
return cachedAuthorized;
}

View File

@ -17,8 +17,9 @@ public class AuditLog {
}
public static void log(User performedBy, String performedByIp, Actor actor, AuditLogActionType actionType, Map<String, Object> actionData) {
AuditLogEntry entry = new AuditLogEntry(performedBy, performedByIp, actor, actionType, actionData);
entry.insert();
APIv3.getStatsD().incrementCounter("apiv3.auditLog.insertions");
APIv3.getDatastore().save(new AuditLogEntry(performedBy, performedByIp, actor, actionType, actionData));
}
}

View File

@ -1,52 +1,53 @@
package net.frozenorb.apiv3.filters;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.*;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Filter;
import spark.Request;
import spark.Response;
import spark.Spark;
import java.util.Base64;
public final class ActorAttributeFilter implements Filter {
public final class ActorAttributeFilter implements Handler<RoutingContext> {
public void handle(Request req, Response res) {
String authHeader = req.headers("Authorization");
String mhqAuthHeader = req.headers("MHQ-Authorization");
// TODO: MAKE THIS ASYNC
public void handle(RoutingContext ctx) {
String authHeader = ctx.request().getHeader("Authorization");
String mhqAuthHeader = ctx.request().getHeader("MHQ-Authorization");
if (authHeader != null) {
req.attribute("actor", processBasicAuthorization(authHeader, res));
processBasicAuthorization(authHeader, ctx);
} else if (mhqAuthHeader != null) {
req.attribute("actor", processMHQAuthorization(mhqAuthHeader));
processMHQAuthorization(mhqAuthHeader, ctx);
} else {
req.attribute("actor", new UnknownActor());
ctx.put("actor", new UnknownActor());
ctx.next();
}
}
@SuppressWarnings("deprecation") // We purposely get the User by their last username.
private Actor processBasicAuthorization(String authHeader, Response res) {
private void processBasicAuthorization(String authHeader, RoutingContext ctx) {
String encodedHeader = authHeader.substring("Basic ".length());
String[] credentials = new String(Base64.getDecoder().decode(encodedHeader.getBytes())).split(":");
if (credentials.length == 2) {
User user = User.byLastUsername(credentials[0]);
User user = User.findByLastUsername(credentials[0]);
String password = credentials[1];
if (user != null && user.getPassword() != null && user.checkPassword(password)) {
return new UserActor(user);
ctx.put("actor", new UserActor(user));
ctx.next();
return;
}
}
res.header("WWW-Authenticate", "Basic realm=\"MineHQ\"");
Spark.halt(401, APIv3.getGson().toJson(ErrorUtils.error("Failed to authorize as " + credentials[0] + ".")));
return null;
ctx.response().putHeader("WWW-Authenticate", "Basic realm=\"MineHQ\"");
ErrorUtils.respondGeneric(ctx, "Failed to authorize as " + credentials[0] + ".");
}
private Actor processMHQAuthorization(String authHeader) {
private void processMHQAuthorization(String authHeader, RoutingContext ctx) {
String[] split = authHeader.split(" ");
if (split.length >= 2) {
@ -57,33 +58,39 @@ public final class ActorAttributeFilter implements Filter {
String properKey = APIv3.getConfig().getProperty("auth.websiteApiKey");
if (givenKey.equals(properKey)) {
return new WebsiteActor();
ctx.put("actor", new WebsiteActor());
ctx.next();
return;
}
} else if (type.equals("Server") && split.length == 3) {
Server server = Server.byId(split[1]);
Server server = Server.findById(split[1]);
if (server == null) {
Spark.halt(401, APIv3.getGson().toJson(ErrorUtils.respondNotFound("Server", split[1])));
ErrorUtils.respondNotFound(ctx, "Server", split[1]);
return;
}
String givenKey = split[2];
String properKey = server.getApiKey();
if (givenKey.equals(properKey)) {
return new ServerActor(server);
ctx.put("actor", new ServerActor(server));
ctx.next();
return;
}
} else if (type.equals("BungeeCord") && split.length == 2) {
String givenKey = split[1];
String properKey = APIv3.getConfig().getProperty("auth.bungeeCordApiKey");
if (givenKey.equals(properKey)) {
return new BungeeCordActor();
ctx.put("actor", new BungeeCordActor());
ctx.next();
return;
}
}
}
Spark.halt(401, APIv3.getGson().toJson(ErrorUtils.error("Failed to authorize.")));
return null;
ErrorUtils.respondGeneric(ctx, "Failed to authorize.");
}
}

View File

@ -1,22 +1,22 @@
package net.frozenorb.apiv3.filters;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Filter;
import spark.Request;
import spark.Response;
import spark.Spark;
public final class AuthorizationFilter implements Filter {
public final class AuthorizationFilter implements Handler<RoutingContext> {
public void handle(Request req, Response res) {
public void handle(RoutingContext ctx) {
Actor actor = ctx.get("actor");
if (!actor.isAuthorized()) {
APIv3.getStatsD().incrementCounter("apiv3.http.unauthorized");
res.header("WWW-Authenticate", "Basic realm=\"MineHQ\"");
Spark.halt(401, APIv3.getGson().toJson(ErrorUtils.error("Unauthorized access: Please authorize as an approved actor. You're currently authorized as " + actor.getName())));
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 {
ctx.next();
}
}

View File

@ -1,13 +0,0 @@
package net.frozenorb.apiv3.filters;
import spark.Filter;
import spark.Request;
import spark.Response;
public final class ContentTypeFilter implements Filter {
public void handle(Request req, Response res) {
res.type("application/json");
}
}

View File

@ -1,21 +0,0 @@
package net.frozenorb.apiv3.filters;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.actors.Actor;
import spark.Filter;
import spark.Request;
import spark.Response;
@Slf4j
public final class LoggingFilter implements Filter {
@Getter @Setter private static boolean debug = false;
public void handle(Request req, Response res) {
Actor actor = ctx.get("actor");
log.info("(" + actor.getName() + " - " + actor.getType() + ") " + req.requestMethod().toUpperCase() + " " + req.pathInfo() + (debug ? "\n" + res.body() : ""));
}
}

View File

@ -1,16 +0,0 @@
package net.frozenorb.apiv3.filters;
import net.frozenorb.apiv3.APIv3;
import spark.Filter;
import spark.Request;
import spark.Response;
public final class MetricsAfterFilter implements Filter {
public void handle(Request req, Response res) {
long started = req.attribute("requestStarted");
APIv3.getStatsD().recordExecutionTime("apiv3.http.executionTime", System.currentTimeMillis() - started);
APIv3.getStatsD().incrementCounter("apiv3.http.requests");
}
}

View File

@ -1,13 +0,0 @@
package net.frozenorb.apiv3.filters;
import spark.Filter;
import spark.Request;
import spark.Response;
public final class MetricsBeforeFilter implements Filter {
public void handle(Request req, Response res) {
req.attribute("requestStarted", System.currentTimeMillis());
}
}

View File

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

View File

@ -1,31 +1,55 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableMap;
import com.mongodb.async.client.MongoCollection;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Entity(value = "auditLog", noClassnameStored = true)
@Entity
public final class AuditLogEntry {
private static final MongoCollection<AuditLogEntry> auditLogCollection = APIv3.getDatabase().getCollection("auditLog", AuditLogEntry.class);
@Getter @Id private String id;
@Getter @Indexed private UUID user;
@Getter private UUID user;
@Getter private String userIp;
@Getter @Indexed private Date performedAt;
@Getter private Date performedAt;
@Getter private String actorName;
@Getter private ActorType actorType;
@Getter private AuditLogActionType type;
@Getter private Map<String, Object> metadata;
public static List<AuditLogEntry> findAll() {
return SyncUtils.blockMulti(auditLogCollection.find());
}
public static AuditLogEntry findById(String id) {
return SyncUtils.blockOne(auditLogCollection.find(new Document("_id", id)));
}
public static List<AuditLogEntry> findByUser(User user) {
return findByUser(user.getId());
}
public static List<AuditLogEntry> findByUser(UUID user) {
return SyncUtils.blockMulti(auditLogCollection.find(new Document("user", user)));
}
public AuditLogEntry() {} // For Morphia
public AuditLogEntry(User user, String userIp, Actor actor, AuditLogActionType type, Map<String, Object> metadata) {
@ -39,4 +63,10 @@ public final class AuditLogEntry {
this.metadata = ImmutableMap.copyOf(metadata);
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
auditLogCollection.insertOne(this, callback);
callback.get();
}
}

View File

@ -1,56 +1,59 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.Collections2;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.utils.UUIDUtils;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.*;
@Entity(value = "grants", noClassnameStored = true)
@Entity
@AllArgsConstructor
public final class Grant {
private static final MongoCollection<Grant> grantsCollection = APIv3.getDatabase().getCollection("grants", Grant.class);
@Getter @Id private String id;
@Getter @Indexed private UUID user;
@Getter private UUID user;
@Getter private String reason;
@Getter private Set<String> scopes = new HashSet<>(); // So on things w/o scopes we still load properly (Morphia drops empty sets)
@Getter @Indexed private String rank;
@Getter private String rank;
@Getter private Date expiresAt;
@Getter private UUID addedBy;
@Getter @Indexed private Date addedAt;
@Getter private Date addedAt;
@Getter private UUID removedBy;
@Getter private Date removedAt;
@Getter private String removalReason;
public static Grant byId(String id) {
return APIv3.getDatastore().createQuery(Grant.class).field("id").equal(id).get();
public static List<Grant> findAll() {
return SyncUtils.blockMulti(grantsCollection.find());
}
public static Map<UUID, List<Grant>> byUserGrouped(Iterable<UUID> ids) {
Map<UUID, List<Grant>> result = new HashMap<>();
Set<UUID> uuidsToSearch = new HashSet<>();
public static List<Grant> findByRank(Iterable<Rank> ranks) {
return SyncUtils.blockMulti(grantsCollection.find(new Document("rank", new Document("$in", ranks))));
}
for (UUID id : ids) {
result.put(id, new ArrayList<>());
public static Grant findById(String id) {
return SyncUtils.blockOne(grantsCollection.find(new Document("_id", id)));
}
if (UUIDUtils.isAcceptableUUID(id)) {
uuidsToSearch.add(id);
}
}
public static List<Grant> findByUser(User user) {
return findByUser(user.getId());
}
APIv3.getDatastore().createQuery(Grant.class).field("user").in(uuidsToSearch).forEach((grant) -> {
result.get(grant.getUser()).add(grant);
});
return result;
public static List<Grant> findByUser(UUID user) {
return SyncUtils.blockMulti(grantsCollection.find(new Document("user", user)));
}
public Grant() {} // For Morphia
@ -66,14 +69,6 @@ public final class Grant {
this.addedAt = new Date();
}
public void delete(User removedBy, String reason) {
this.removedBy = removedBy.getId();
this.removedAt = new Date();
this.removalReason = reason;
APIv3.getDatastore().save(this);
}
public boolean isActive() {
return !(isExpired() || isRemoved());
}
@ -98,4 +93,20 @@ public final class Grant {
return scopes.isEmpty();
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
grantsCollection.insertOne(this, callback);
callback.get();
}
public void delete(User removedBy, String reason) {
this.removedBy = removedBy.getId();
this.removedAt = new Date();
this.removalReason = reason;
BlockingCallback<DeleteResult> callback = new BlockingCallback<>();
grantsCollection.deleteOne(new Document("_id", id), callback);
callback.get();
}
}

View File

@ -1,31 +1,57 @@
package net.frozenorb.apiv3.models;
import com.mongodb.async.client.MongoCollection;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.*;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Entity(value = "ipLog", noClassnameStored = true)
@Entity
@AllArgsConstructor
@Indexes(
@Index(fields = {
@Field("user"),
@Field("userIp")
})
)
public final class IPLogEntry {
private static final MongoCollection<IPLogEntry> ipLogCollection = APIv3.getDatabase().getCollection("ipLog", IPLogEntry.class);
@Getter @Id private String id;
@Getter @Indexed private UUID user;
@Getter @Indexed private String userIp;
@Getter private UUID user;
@Getter private String userIp;
@Getter private Date firstSeenAt;
@Getter private Date lastSeenAt;
@Getter private int uses;
public static List<IPLogEntry> findAll() {
return SyncUtils.blockMulti(ipLogCollection.find());
}
public static IPLogEntry findById(String id) {
return SyncUtils.blockOne(ipLogCollection.find(new Document("_id", id)));
}
public static List<IPLogEntry> findByUser(User user) {
return findByUser(user.getId());
}
public static List<IPLogEntry> findByUser(UUID user) {
return SyncUtils.blockMulti(ipLogCollection.find(new Document("user", user)));
}
public static IPLogEntry findByUserAndIp(User user, String userIp) {
return findByUserAndIp(user.getId(), userIp);
}
public static IPLogEntry findByUserAndIp(UUID user, String userIp) {
return SyncUtils.blockOne(ipLogCollection.find(new Document("user", user).append("userIp", userIp)));
}
public IPLogEntry() {} // For Morphia
public IPLogEntry(User user, String userIp) {
@ -40,8 +66,12 @@ public final class IPLogEntry {
public void used() {
this.lastSeenAt = new Date();
this.uses++;
}
APIv3.getDatastore().save(this);
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
ipLogCollection.insertOne(this, callback);
callback.get();
}
}

View File

@ -1,26 +1,35 @@
package net.frozenorb.apiv3.models;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import java.util.List;
import java.util.Map;
@Entity(value = "notificationTemplates", noClassnameStored = true)
@Entity
public final class NotificationTemplate {
@Getter @Id private String id;
@Getter private String subject;
@Getter private String body;
private static final MongoCollection<NotificationTemplate> notificationTemplatesCollection = APIv3.getDatabase().getCollection("notificationTemplates", NotificationTemplate.class);
public static NotificationTemplate byId(String id) {
return APIv3.getDatastore().createQuery(NotificationTemplate.class).field("id").equal(id).get();
@Getter @Id private String id;
@Getter @Setter private String subject;
@Getter @Setter private String body;
public static List<NotificationTemplate> findAll() {
return SyncUtils.blockMulti(notificationTemplatesCollection.find());
}
public static List<NotificationTemplate> values() {
return APIv3.getDatastore().createQuery(NotificationTemplate.class).asList();
public static NotificationTemplate findById(String id) {
return SyncUtils.blockOne(notificationTemplatesCollection.find(new Document("_id", id)));
}
public NotificationTemplate() {} // For Morphia
@ -31,17 +40,6 @@ public final class NotificationTemplate {
this.body = body;
}
public void update(String subject, String body) {
this.subject = subject;
this.body = body;
APIv3.getDatastore().save(this);
}
public void delete() {
APIv3.getDatastore().delete(this);
}
public String fillSubject(Map<String, Object> replacements) {
return fill(subject, replacements);
}
@ -61,4 +59,22 @@ public final class NotificationTemplate {
return working;
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
notificationTemplatesCollection.insertOne(this, callback);
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
notificationTemplatesCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
public void delete() {
BlockingCallback<DeleteResult> callback = new BlockingCallback<>();
notificationTemplatesCollection.deleteOne(new Document("_id", id), callback);
callback.get();
}
}

View File

@ -1,37 +1,37 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableSet;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.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 net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.*;
import java.util.*;
@Entity(value = "punishments", noClassnameStored = true)
@Entity
@AllArgsConstructor
@Indexes(
@Index(fields = {
@Field("user"),
@Field("type")
})
)
public final class Punishment {
private static final MongoCollection<Punishment> punishmentsCollection = APIv3.getDatabase().getCollection("punishments", Punishment.class);
@Getter @Id private String id;
@Getter @Indexed private UUID user;
@Getter private UUID user;
@Getter private String reason;
@Getter @Indexed private PunishmentType type; // Type is indexed for the rank dump
@Getter private PunishmentType type;
@Getter private Date expiresAt;
@Getter private Map<String, Object> metadata;
@Getter private UUID addedBy;
@Getter @Indexed private Date addedAt;
@Getter private Date addedAt;
@Getter private String actorName;
@Getter private ActorType actorType;
@ -39,31 +39,32 @@ public final class Punishment {
@Getter private Date removedAt;
@Getter private String removalReason;
public static Punishment byId(String id) {
return APIv3.getDatastore().createQuery(Punishment.class).field("id").equal(id).get();
public static List<Punishment> findAll() {
return SyncUtils.blockMulti(punishmentsCollection.find());
}
public static Map<UUID, List<Punishment>> byUserGrouped(Iterable<UUID> ids) {
return byUserGrouped(ids, ImmutableSet.copyOf(PunishmentType.values()));
public static List<Punishment> findByType(Iterable<PunishmentType> types) {
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("type", new Document("$in", types))));
}
public static Map<UUID, List<Punishment>> byUserGrouped(Iterable<UUID> ids, Iterable<Punishment.PunishmentType> types) {
Map<UUID, List<Punishment>> result = new HashMap<>();
Set<UUID> uuidsToSearch = new HashSet<>();
public static Punishment findById(String id) {
return SyncUtils.blockOne(punishmentsCollection.find(new Document("_id", id)));
}
for (UUID id : ids) {
result.put(id, new ArrayList<>());
public static List<Punishment> findByUser(User user) {
return findByUser(user.getId());
}
if (UUIDUtils.isAcceptableUUID(id)) {
uuidsToSearch.add(id);
}
}
public static List<Punishment> findByUser(UUID user) {
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("user", user)));
}
APIv3.getDatastore().createQuery(Punishment.class).field("user").in(uuidsToSearch).field("type").in(types).forEach((punishment) -> {
result.get(punishment.getUser()).add(punishment);
});
public static List<Punishment> findByUserAndType(User user, Iterable<PunishmentType> types) {
return findByUserAndType(user.getId(), types);
}
return result;
public static List<Punishment> findByUserAndType(UUID user, Iterable<PunishmentType> types) {
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("user", user).append("type", new Document("$in", types))));
}
public Punishment() {} // For Morphia
@ -81,14 +82,6 @@ public final class Punishment {
this.metadata = metadata;
}
public void delete(User removedBy, String reason) {
this.removedBy = removedBy.getId();
this.removedAt = new Date();
this.removalReason = reason;
APIv3.getDatastore().save(this);
}
public boolean isActive() {
return !(isExpired() || isRemoved());
}
@ -124,6 +117,22 @@ public final class Punishment {
}
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
punishmentsCollection.insertOne(this, callback);
callback.get();
}
public void delete(User removedBy, String reason) {
this.removedBy = removedBy.getId();
this.removedAt = new Date();
this.removalReason = reason;
BlockingCallback<DeleteResult> callback = new BlockingCallback<>();
punishmentsCollection.deleteOne(new Document("_id", id), callback);
callback.get();
}
public enum PunishmentType {
BLACKLIST, BAN, MUTE, WARN

View File

@ -1,11 +1,17 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Ints;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import java.util.ArrayList;
import java.util.HashMap;
@ -13,9 +19,11 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Entity(value = "ranks", noClassnameStored = true)
@Entity
public final class Rank {
private static final MongoCollection<Rank> ranksCollection = APIv3.getDatabase().getCollection("ranks", Rank.class);
private static Map<String, Rank> rankCache = null;
private static List<Rank> rankAltCache = null;
private static long rankCacheUpdated = 0;
@ -25,18 +33,18 @@ public final class Rank {
@Getter private String displayName;
@Getter private String gameColor;
@Getter private String websiteColor;
@Getter @Indexed private boolean staffRank;
@Getter private boolean staffRank;
public static Rank byId(String id) {
updateCacheIfNeeded();
return rankCache.get(id);
}
public static List<Rank> values() {
public static List<Rank> findAll() {
updateCacheIfNeeded();
return ImmutableList.copyOf(rankAltCache);
}
public static Rank findById(String id) {
updateCacheIfNeeded();
return rankCache.get(id);
}
public Rank() {} // For Morphia
public Rank(String id, int weight, String displayName, String gameColor, String websiteColor, boolean staffRank) {
@ -48,16 +56,14 @@ public final class Rank {
this.staffRank = staffRank;
}
public void delete() {
APIv3.getDatastore().delete(this);
}
private static void updateCacheIfNeeded() {
if (rankCache == null || (System.currentTimeMillis() - rankCacheUpdated) > TimeUnit.MINUTES.toMillis(1)) {
Map<String, Rank> working = new HashMap<>();
List<Rank> workingAlt = new ArrayList<>();
List<Rank> allRanks = SyncUtils.blockMulti(ranksCollection.find());
allRanks.sort((a, b) -> Ints.compare(a.getWeight(), b.getWeight()));
for (Rank rank : APIv3.getDatastore().createQuery(Rank.class).order("weight").asList()) {
for (Rank rank : allRanks) {
working.put(rank.getId(), rank);
workingAlt.add(rank);
}
@ -68,4 +74,22 @@ public final class Rank {
}
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
ranksCollection.insertOne(this, callback);
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
ranksCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
public void delete() {
BlockingCallback<DeleteResult> callback = new BlockingCallback<>();
ranksCollection.deleteOne(new Document("_id", id), callback);
callback.get();
}
}

View File

@ -1,19 +1,27 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableSet;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Entity(value = "servers", noClassnameStored = true)
@Entity
public final class Server {
private static final MongoCollection<Server> serversCollection = APIv3.getDatabase().getCollection("servers", Server.class);
private static Map<String, Server> serverCache = null;
private static List<Server> serverCacheAlt = null;
private static long serverCacheUpdated = 0;
@ -21,22 +29,22 @@ public final class Server {
@Getter @Id private String id;
@Getter private String displayName;
@Getter @ExcludeFromReplies String apiKey;
@Getter @Indexed private String serverGroup;
@Getter private String serverGroup;
@Getter private String serverIp;
@Getter @Setter private Date lastUpdatedAt;
@Getter @Setter private double lastTps;
@Getter @Setter @ExcludeFromReplies private Set<UUID> players;
public static Server byId(String id) {
updateCacheIfNeeded();
return serverCache.get(id);
}
public static List<Server> values() {
public static List<Server> findAll() {
updateCacheIfNeeded();
return serverCacheAlt;
}
public static Server findById(String id) {
updateCacheIfNeeded();
return serverCache.get(id);
}
public Server() {} // For Morphia
public Server(String id, String displayName, String apiKey, ServerGroup serverGroup, String serverIp) {
@ -50,16 +58,12 @@ public final class Server {
this.players = new HashSet<>();
}
public void delete() {
APIv3.getDatastore().delete(this);
}
private static void updateCacheIfNeeded() {
if (serverCache == null || (System.currentTimeMillis() - serverCacheUpdated) > TimeUnit.MINUTES.toMillis(1)) {
Map<String, Server> working = new HashMap<>();
List<Server> workingAlt = new ArrayList<>();
for (Server server : APIv3.getDatastore().createQuery(Server.class).asList()) {
for (Server server : SyncUtils.blockMulti(serversCollection.find())) {
working.put(server.getId(), server);
workingAlt.add(server);
}
@ -70,4 +74,28 @@ public final class Server {
}
}
public void receivedHeartbeat(double tps, Iterable<UUID> players) {
this.lastUpdatedAt = new Date();
this.lastTps = tps;
this.players = ImmutableSet.copyOf(players);
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
serversCollection.insertOne(this, callback);
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
serversCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
public void delete() {
BlockingCallback<DeleteResult> callback = new BlockingCallback<>();
serversCollection.deleteOne(new Document("_id", id), callback);
callback.get();
}
}

View File

@ -1,44 +1,51 @@
package net.frozenorb.apiv3.models;
import com.google.gson.annotations.SerializedName;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.PermissionUtils;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Property;
import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Entity(value = "serverGroups", noClassnameStored = true)
@Entity
public final class ServerGroup {
private static final MongoCollection<ServerGroup> serverGroupsCollection = APIv3.getDatabase().getCollection("serverGroups", ServerGroup.class);
private static Map<String, ServerGroup> serverGroupCache = null;
private static List<ServerGroup> serverGroupAltCache = null;
private static long serverGroupCacheUpdated = 0;
@Getter @Id private String id;
@Getter private String image;
// We rename this to public, we just can't name it that because it's a Java identifier.
@Getter @Property("public") @SerializedName("public") private boolean isPublic;
// We rename this to public (only to gson), we just can't name it that because it's a Java identifier.
@Getter @SerializedName("public") private boolean isPublic;
// We define these HashSets up here because, in the event they're
// empty, Morphia will load them as null, not empty sets.
@Getter @Setter @ExcludeFromReplies private Set<String> announcements = new HashSet<>();
@Getter @Setter @ExcludeFromReplies private Map<String, List<String>> permissions = new HashMap<>();
public static ServerGroup byId(String id) {
updateCacheIfNeeded();
return serverGroupCache.get(id);
}
public static List<ServerGroup> values() {
public static List<ServerGroup> findAll() {
updateCacheIfNeeded();
return serverGroupAltCache;
}
public static ServerGroup findById(String id) {
updateCacheIfNeeded();
return serverGroupCache.get(id);
}
public ServerGroup() {} // For Morphia
public ServerGroup(String id, String image, boolean isPublic) {
@ -51,16 +58,12 @@ public final class ServerGroup {
return PermissionUtils.mergeUpTo(permissions, userRank);
}
public void delete() {
APIv3.getDatastore().delete(this);
}
private static void updateCacheIfNeeded() {
if (serverGroupCache == null || (System.currentTimeMillis() - serverGroupCacheUpdated) > TimeUnit.MINUTES.toMillis(1)) {
Map<String, ServerGroup> working = new HashMap<>();
List<ServerGroup> workingAlt = new ArrayList<>();
for (ServerGroup serverGroup : APIv3.getDatastore().createQuery(ServerGroup.class).asList()) {
for (ServerGroup serverGroup : SyncUtils.blockMulti(serverGroupsCollection.find())) {
working.put(serverGroup.getId(), serverGroup);
workingAlt.add(serverGroup);
}
@ -71,4 +74,22 @@ public final class ServerGroup {
}
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
serverGroupsCollection.insertOne(this, callback);
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
serverGroupsCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
public void delete() {
BlockingCallback<DeleteResult> callback = new BlockingCallback<>();
serverGroupsCollection.deleteOne(new Document("_id", id), callback);
callback.get();
}
}

View File

@ -4,30 +4,35 @@ import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.hash.Hashing;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.UpdateResult;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.MojangUtils;
import net.frozenorb.apiv3.utils.PermissionUtils;
import net.frozenorb.apiv3.utils.SyncUtils;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.Document;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.*;
@Entity(value = "users", noClassnameStored = true)
@Entity
@AllArgsConstructor
public final class User {
public final class User {
private static final MongoCollection<User> usersCollection = APIv3.getDatabase().getCollection("users", User.class);
@Getter @Id private UUID id;
@Getter @Indexed private String lastUsername;
@Getter private String lastUsername;
@Getter @ExcludeFromReplies private Map<String, Date> aliases = new HashMap<>();
@Getter @Setter @ExcludeFromReplies private String totpSecret;
@Getter @Indexed @ExcludeFromReplies @Setter private String emailToken;
@Getter @ExcludeFromReplies @Setter private String totpSecret;
@Getter @ExcludeFromReplies @Setter private String emailToken;
@Getter @ExcludeFromReplies @Setter private Date emailTokenSetAt;
@Getter @ExcludeFromReplies private String password;
@Getter @Setter private String email;
@ -37,48 +42,36 @@ public final class User {
@Getter private Date firstSeenAt;
@Getter private boolean online;
public static User byId(String id) {
try {
return byId(UUID.fromString(id));
} catch (Exception ex) {
return null;
}
public static List<User> findAll() {
return SyncUtils.blockMulti(usersCollection.find());
}
public static User byId(UUID id) {
public static User findById(String id) {
UUID uuid;
try {
uuid = UUID.fromString(id);
} catch (IllegalArgumentException ex) {
return null;
}
return findById(uuid);
}
public static User findById(UUID id) {
if (UUIDUtils.isAcceptableUUID(id)) {
return APIv3.getDatastore().createQuery(User.class).field("id").equal(id).get();
return SyncUtils.blockOne(usersCollection.find(new Document("_id", id)));
} else {
return null;
}
}
public static Map<UUID, User> byIdGrouped(Iterable<UUID> ids) {
Map<UUID, User> result = new HashMap<>();
Set<UUID> uuidsToSearch = new HashSet<>();
for (UUID id : ids) {
result.put(id, null);
if (UUIDUtils.isAcceptableUUID(id)) {
uuidsToSearch.add(id);
}
}
APIv3.getDatastore().createQuery(User.class).field("id").in(uuidsToSearch).forEach((user) -> {
result.put(user.getId(), user);
});
return result;
public static User findByEmailToken(String emailToken) {
return SyncUtils.blockOne(usersCollection.find(new Document("emailToken", emailToken)));
}
public static User byEmailToken(String name) {
return APIv3.getDatastore().createQuery(User.class).field("emailToken").equal(name).get();
}
@Deprecated
public static User byLastUsername(String lastUsername) {
return APIv3.getDatastore().createQuery(User.class).field("lastUsername").equal(lastUsername).get();
public static User findByLastUsername(String lastUsername) {
return SyncUtils.blockOne(usersCollection.find(new Document("lastUsername", lastUsername)));
}
public User() {} // For Morphia
@ -98,18 +91,8 @@ public final class User {
updateUsername(lastUsername);
}
public boolean hasPermissionScoped(String permission, ServerGroup scope) {
Rank highestRank = getHighestRank(scope);
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
PermissionUtils.getDefaultPermissions(highestRank),
scope.calculatePermissions(highestRank)
);
return scopedPermissions.containsKey(permission) && scopedPermissions.get(permission);
}
public boolean hasPermissionAnywhere(String permission) {
Map<String, Boolean> globalPermissions = PermissionUtils.getDefaultPermissions(getHighestRank());
Map<String, Boolean> globalPermissions = PermissionUtils.getDefaultPermissions(getHighestRankAnywhere());
for (Map.Entry<ServerGroup, Rank> serverGroupEntry : getHighestRanks().entrySet()) {
ServerGroup serverGroup = serverGroupEntry.getKey();
@ -124,48 +107,7 @@ public final class User {
return globalPermissions.containsKey(permission) && globalPermissions.get(permission);
}
public List<Grant> getGrants() {
return APIv3.getDatastore().createQuery(Grant.class).field("user").equal(id).asList();
}
public List<IPLogEntry> getIPLog() {
return APIv3.getDatastore().createQuery(IPLogEntry.class).field("user").equal(id).asList();
}
public IPLogEntry getIPLogEntry(String ip) {
IPLogEntry existing = APIv3.getDatastore().createQuery(IPLogEntry.class).field("user").equal(id).field("userIp").equal(ip).get();
if (existing == null) {
existing = new IPLogEntry(this, ip);
APIv3.getDatastore().save(existing);
}
return existing;
}
public List<Punishment> getPunishments() {
return APIv3.getDatastore().createQuery(Punishment.class).field("user").equal(id).asList();
}
public List<Punishment> getPunishments(Iterable<Punishment.PunishmentType> types) {
return APIv3.getDatastore().createQuery(Punishment.class).field("user").equal(id).field("type").in(types).asList();
}
public UserMetaEntry getMeta(ServerGroup group) {
return APIv3.getDatastore().createQuery(UserMetaEntry.class).field("user").equal(id).field("serverGroup").equal(group.getId()).get();
}
public void saveMeta(ServerGroup group, Document data) {
UserMetaEntry entry = getMeta(group);
if (entry == null) {
APIv3.getDatastore().save(new UserMetaEntry(this, group, data));
} else {
entry.setData(data);
APIv3.getDatastore().save(entry);
}
}
// TODO: Clean
public boolean seenOnServer(Server server) {
if (online && server.getId().equals(this.lastSeenOn)) {
return false;
@ -192,14 +134,13 @@ public final class User {
User withNewUsername;
while ((withNewUsername = User.byLastUsername(username)) != null) {
while ((withNewUsername = User.findByLastUsername(username)) != null) {
String newUsername = MojangUtils.getName(withNewUsername.getId());
withNewUsername.updateUsername(newUsername);
}
}
this.aliases.put(username, new Date());
APIv3.getDatastore().save(this);
}
public void setPassword(String input) {
@ -218,16 +159,17 @@ public final class User {
return hashed.equals(password);
}
public Rank getHighestRank() {
return getHighestRank(null);
public Rank getHighestRankAnywhere() {
return getHighestRankScoped(null);
}
public Rank getHighestRank(ServerGroup serverGroup) {
return getHighestRank(serverGroup, getGrants());
public Rank getHighestRankScoped(ServerGroup serverGroup) {
return getHighestRankScoped(serverGroup, Grant.findByUser(this));
}
// TODO: Clean
// This is only used to help batch requests to mongo
public Rank getHighestRank(ServerGroup serverGroup, Iterable<Grant> grants) {
public Rank getHighestRankScoped(ServerGroup serverGroup, Iterable<Grant> grants) {
Rank highest = null;
for (Grant grant : grants) {
@ -235,7 +177,7 @@ public final class User {
continue;
}
Rank rank = Rank.byId(grant.getRank());
Rank rank = Rank.findById(grant.getRank());
if (highest == null || rank.getWeight() > highest.getWeight()) {
highest = rank;
@ -245,16 +187,17 @@ public final class User {
if (highest != null) {
return highest;
} else {
return Rank.byId("default");
return Rank.findById("default");
}
}
// TODO: Clean
public Map<ServerGroup, Rank> getHighestRanks() {
Map<ServerGroup, Rank> highestRanks = new HashMap<>();
Rank defaultRank = Rank.byId("default");
List<Grant> userGrants = getGrants();
Rank defaultRank = Rank.findById("default");
List<Grant> userGrants = Grant.findByUser(this);
for (ServerGroup serverGroup : ServerGroup.values()) {
for (ServerGroup serverGroup : ServerGroup.findAll()) {
Rank highest = defaultRank;
for (Grant grant : userGrants) {
@ -262,7 +205,7 @@ public final class User {
continue;
}
Rank rank = Rank.byId(grant.getRank());
Rank rank = Rank.findById(grant.getRank());
if (highest == null || rank.getWeight() > highest.getWeight()) {
highest = rank;
@ -276,19 +219,19 @@ public final class User {
}
public Map<String, Object> getLoginInfo(Server server) {
return getLoginInfo(
return createLoginInfo(
server,
getPunishments(ImmutableSet.of(
Punishment.findByUserAndType(this, ImmutableSet.of(
Punishment.PunishmentType.BLACKLIST,
Punishment.PunishmentType.BAN,
Punishment.PunishmentType.MUTE
)),
getGrants()
Grant.findByUser(this)
);
}
// This is only used to help batch requests to mongo
public Map<String, Object> getLoginInfo(Server server, Iterable<Punishment> punishments, Iterable<Grant> grants) {
public Map<String, Object> createLoginInfo(Server server, Iterable<Punishment> punishments, Iterable<Grant> grants) {
Punishment activeMute = null;
String accessDenialReason = null;
@ -304,7 +247,7 @@ public final class User {
}
}
Rank highestRank = getHighestRank(ServerGroup.byId(server.getServerGroup()), grants);
Rank highestRank = getHighestRankScoped(ServerGroup.findById(server.getServerGroup()), grants);
// Generics are weird, yes we have to do this.
ImmutableMap.Builder<String, Object> result = ImmutableMap.<String, Object>builder()
@ -323,4 +266,16 @@ public final class User {
return result.build();
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
usersCollection.insertOne(this, callback);
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
usersCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
}

View File

@ -1,25 +1,49 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableMap;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Entity(value = "userMeta", noClassnameStored = true)
@Entity
public final class UserMetaEntry {
private static final MongoCollection<UserMetaEntry> userMetaCollection = APIv3.getDatabase().getCollection("userMeta", UserMetaEntry.class);
@Getter @Id private String id;
@Getter @Indexed private UUID user;
@Getter @Indexed private String serverGroup;
@Getter private UUID user;
@Getter private String serverGroup;
@Getter @Setter private Map<String, Object> data;
public static List<UserMetaEntry> findAll() {
return SyncUtils.blockMulti(userMetaCollection.find());
}
public static UserMetaEntry findById(String id) {
return SyncUtils.blockOne(userMetaCollection.find(new Document("_id", id)));
}
public static UserMetaEntry findByUserAndGroup(User user, ServerGroup serverGroup) {
return findByUserAndGroup(user.getId(), serverGroup);
}
public static UserMetaEntry findByUserAndGroup(UUID user, ServerGroup serverGroup) {
return SyncUtils.blockOne(userMetaCollection.find(new Document("user", user).append("serverGroup", serverGroup.getId())));
}
public UserMetaEntry() {} // For Morphia
public UserMetaEntry(User user, ServerGroup serverGroup, Map<String, Object> data) {
@ -29,8 +53,22 @@ public final class UserMetaEntry {
this.data = ImmutableMap.copyOf(data);
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
userMetaCollection.insertOne(this, callback);
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
userMetaCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
public void delete() {
APIv3.getDatastore().delete(this);
BlockingCallback<DeleteResult> callback = new BlockingCallback<>();
userMetaCollection.deleteOne(new Document("_id", id), callback);
callback.get();
}
}

View File

@ -1,6 +1,7 @@
package net.frozenorb.apiv3.routes;
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.Grant;
@ -28,7 +29,8 @@ public final class GETDump implements Handler<RoutingContext> {
List<UUID> banCache = new ArrayList<>();
List<UUID> blacklistCache = new ArrayList<>();
APIv3.getDatastore().createQuery(Punishment.class).field("type").in(ImmutableSet.of(
Punishment.findByType
(ImmutableSet.of(
Punishment.PunishmentType.BAN,
Punishment.PunishmentType.BLACKLIST
)).forEach((punishment) -> {
@ -50,7 +52,7 @@ public final class GETDump implements Handler<RoutingContext> {
if (tick == 0 || tick % 2 == 0) {
Map<String, List<UUID>> grantCache = new HashMap<>();
APIv3.getDatastore().createQuery(Grant.class).forEach((grant) -> {
Grant.findAll().forEach((grant) -> {
if (grant.isActive()) {
List<UUID> users = grantCache.get(grant.getRank());
@ -88,21 +90,25 @@ public final class GETDump implements Handler<RoutingContext> {
switch (type.toLowerCase()) {
case "ban":
return banCache;
APIv3.respondJson(ctx, banCache);
return;
case "blacklist":
return blacklistCache;
APIv3.respondJson(ctx, blacklistCache);
return;
case "accessdeniable": // Lowercase d because we convert to lowercase above
List<UUID> result = new ArrayList<>();
result.addAll(banCache);
result.addAll(blacklistCache);
return result;
APIv3.respondJson(ctx, result);
return;
case "grant":
return grantCache;
APIv3.respondJson(ctx, grantCache);
return;
default:
ErrorUtils.respondInvalidInput(ctx, "type", type + " is not a valid type. Not in [ban, blacklist, accessDeniable, grant]");
return
ErrorUtils.respondInvalidInput(ctx, type + " is not a valid type. Not in [ban, blacklist, accessDeniable, grant]");
return;
}
}

View File

@ -1,6 +1,9 @@
package net.frozenorb.apiv3.routes;
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.actors.Actor;
public final class GETWhoAmI implements Handler<RoutingContext> {
@ -8,11 +11,11 @@ public final class GETWhoAmI implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
Actor actor = ctx.get("actor");
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"name", actor.getName(),
"type", actor.getType(),
"authorized", actor.isAuthorized()
);
));
}
}

View File

@ -1,17 +0,0 @@
package net.frozenorb.apiv3.routes;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Spark;
@Slf4j
public final class NotFound implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
log.info(req.requestMethod().toUpperCase() + " " + req.url());
Spark.halt(404, APIv3.getGson().toJson(ErrorUtils.respondNotFound("Route", req.url())));
return null;
}
}

View File

@ -1,13 +1,14 @@
package net.frozenorb.apiv3.routes;
import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
public final class POSTMetrics implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
//LibratoBatch batch = new LibratoBatch();
return ImmutableMap.of();
APIv3.respondJson(ctx, ImmutableMap.of());
}
}

View File

@ -21,9 +21,9 @@ public final class GETAnnouncements implements Handler<RoutingContext> {
}
Server sender = ((ServerActor) actor).getServer();
ServerGroup senderGroup = ServerGroup.byId(sender.getServerGroup());
ServerGroup senderGroup = ServerGroup.findById(sender.getServerGroup());
APIv3.respond(ctx, senderGroup.getAnnouncements());
APIv3.respondJson(ctx, senderGroup.getAnnouncements());
}
}

View File

@ -13,7 +13,7 @@ public final class GETAuditLog implements Handler<RoutingContext> {
int limit = ctx.request().getParam("limit") == null ? 100 : Integer.parseInt(ctx.request().getParam("limit"));
int offset = ctx.request().getParam("offset") == null ? 0 : Integer.parseInt(ctx.request().getParam("offset"));
APIv3.respond(ctx, APIv3.getDatastore().createQuery(AuditLogEntry.class).order("performedAt").limit(limit).offset(offset).asList());
APIv3.respondJson(ctx, APIv3.getDatastore().createQuery(AuditLogEntry.class).order("performedAt").limit(limit).offset(offset).asList());
} catch (NumberFormatException ex) {
ErrorUtils.respondInvalidInput(ctx, "limit/offset must be numerical inputs.");
}

View File

@ -1,7 +1,6 @@
package net.frozenorb.apiv3.routes.chatFilterList;
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;
@ -9,7 +8,7 @@ import net.frozenorb.apiv3.APIv3;
public final class GETChatFilterList implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
APIv3.respond(ctx, ImmutableMap.of());
APIv3.respondJson(ctx, ImmutableMap.of());
}
}

View File

@ -13,7 +13,7 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETEGrant implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
Grant grant = Grant.byId(ctx.request().getParam("id"));
Grant grant = Grant.findById(ctx.request().getParam("id"));
if (grant == null) {
ErrorUtils.respondNotFound(ctx, "Grant", ctx.request().getParam("id"));
@ -23,7 +23,7 @@ public final class DELETEGrant implements Handler<RoutingContext> {
return;
}
User removedBy = User.byId(ctx.request().getParam("removedBy"));
User removedBy = User.findById(ctx.request().getParam("removedBy"));
if (removedBy == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("removedBy"));
@ -39,7 +39,7 @@ public final class DELETEGrant implements Handler<RoutingContext> {
grant.delete(removedBy, reason);
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_GRANT, ImmutableMap.of());
APIv3.respond(ctx, grant);
APIv3.respondJson(ctx, grant);
}
}

View File

@ -8,7 +8,7 @@ import net.frozenorb.apiv3.models.Grant;
public final class GETGrant implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
APIv3.respond(ctx, Grant.byId(ctx.request().getParam("id")));
APIv3.respondJson(ctx, Grant.findById(ctx.request().getParam("id")));
}
}

View File

@ -13,7 +13,7 @@ public final class GETGrants implements Handler<RoutingContext> {
int limit = ctx.request().getParam("limit") == null ? 100 : Integer.parseInt(ctx.request().getParam("limit"));
int offset = ctx.request().getParam("offset") == null ? 0 : Integer.parseInt(ctx.request().getParam("offset"));
APIv3.respond(ctx, APIv3.getDatastore().createQuery(Grant.class).order("addedAt").limit(limit).offset(offset).asList());
APIv3.respondJson(ctx, APIv3.getDatastore().createQuery(Grant.class).order("addedAt").limit(limit).offset(offset).asList());
} catch (NumberFormatException ex) {
ErrorUtils.respondInvalidInput(ctx, "limit and offset must be numerical inputs.");
}

View File

@ -3,20 +3,21 @@ package net.frozenorb.apiv3.routes.grants;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserGrants implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.byId(ctx.request().getParam("id"));
User target = User.findById(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
APIv3.respond(ctx, target.getGrants());
APIv3.respondJson(ctx, Grant.findByUser(target));
}
}

View File

@ -1,5 +1,7 @@
package net.frozenorb.apiv3.routes.grants;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.Rank;
@ -14,16 +16,18 @@ import java.util.Set;
public final class POSTUserGrant implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.byId(ctx.request().getParam("id"));
User target = User.findById(ctx.request().getParam("id"));
if (target == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
String reason = ctx.request().getParam("reason");
if (reason == null || reason.trim().isEmpty()) {
return ErrorUtils.respondRequiredInput("reason");
ErrorUtils.respondRequiredInput(ctx, "reason");
return;
}
Set<ServerGroup> scopes = new HashSet<>();
@ -31,20 +35,22 @@ public final class POSTUserGrant implements Handler<RoutingContext> {
if (!scopesUnparsed.isEmpty()) {
for (String serverGroupId : scopesUnparsed.split(",")) {
ServerGroup serverGroup = ServerGroup.byId(serverGroupId);
ServerGroup serverGroup = ServerGroup.findById(serverGroupId);
if (serverGroup == null) {
return ErrorUtils.respondNotFound("Server group", serverGroupId);
ErrorUtils.respondNotFound(ctx, "Server group", serverGroupId);
return;
}
scopes.add(serverGroup);
}
}
Rank rank = Rank.byId(ctx.request().getParam("rank"));
Rank rank = Rank.findById(ctx.request().getParam("rank"));
if (rank == null) {
return ErrorUtils.respondNotFound("Rank", ctx.request().getParam("rank"));
ErrorUtils.respondNotFound(ctx, "Rank", ctx.request().getParam("rank"));
return;
}
Date expiresAt;
@ -56,15 +62,16 @@ public final class POSTUserGrant implements Handler<RoutingContext> {
}
if (expiresAt != null && expiresAt.before(new Date())) {
return ErrorUtils.respondInvalidInput("Expiration date cannot be in the past.");
ErrorUtils.respondInvalidInput(ctx, "Expiration date cannot be in the past.");
return;
}
// We purposely don't do a null check, grants don't have to have a source.
User addedBy = User.byId(ctx.request().getParam("addedBy"));
User addedBy = User.findById(ctx.request().getParam("addedBy"));
Grant grant = new Grant(target, reason, scopes, rank, expiresAt, addedBy);
APIv3.getDatastore().save(grant);
return grant;
grant.insert();
APIv3.respondJson(ctx, grant);
}
}

View File

@ -3,20 +3,21 @@ package net.frozenorb.apiv3.routes.ipLog;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.IPLogEntry;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserIPLog implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.byId(ctx.request().getParam("id"));
User target = User.findById(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
APIv3.respond(ctx, target.getIPLog());
APIv3.respondJson(ctx, IPLogEntry.findByUser(target));
}
}

View File

@ -9,7 +9,7 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETENotificationTemplate implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
NotificationTemplate notificationTemplate = NotificationTemplate.byId(ctx.request().getParam("id"));
NotificationTemplate notificationTemplate = NotificationTemplate.findById(ctx.request().getParam("id"));
if (notificationTemplate == null) {
ErrorUtils.respondNotFound(ctx, "Notification template", ctx.request().getParam("id"));
@ -17,7 +17,7 @@ public final class DELETENotificationTemplate implements Handler<RoutingContext>
}
notificationTemplate.delete();
APIv3.respond(ctx, notificationTemplate);
APIv3.respondJson(ctx, notificationTemplate);
}
}

View File

@ -8,7 +8,7 @@ import net.frozenorb.apiv3.models.NotificationTemplate;
public final class GETNotificationTemplate implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
APIv3.respond(ctx, NotificationTemplate.byId(ctx.request().getParam("id")));
APIv3.respondJson(ctx, NotificationTemplate.findById(ctx.request().getParam("id")));
}
}

View File

@ -8,7 +8,7 @@ import net.frozenorb.apiv3.models.NotificationTemplate;
public final class GETNotificationTemplates implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
APIv3.respond(ctx, NotificationTemplate.values());
APIv3.respondJson(ctx, NotificationTemplate.findAll());
}
}

View File

@ -13,8 +13,8 @@ public final class POSTNotificationTemplate implements Handler<RoutingContext> {
String body = ctx.request().getParam("body");
NotificationTemplate notificationTemplate = new NotificationTemplate(id, subject, body);
APIv3.getDatastore().save(notificationTemplate);
APIv3.respond(ctx, notificationTemplate);
notificationTemplate.insert();
APIv3.respondJson(ctx, notificationTemplate);
}
}

View File

@ -1,6 +1,9 @@
package net.frozenorb.apiv3.routes.punishments;
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.Punishment;
@ -10,29 +13,33 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETEPunishment implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
Punishment punishment = Punishment.byId(ctx.request().getParam("id"));
Punishment punishment = Punishment.findById(ctx.request().getParam("id"));
if (punishment == null) {
return ErrorUtils.respondNotFound("Punishment", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "Punishment", ctx.request().getParam("id"));
return;
} else if (!punishment.isActive()) {
return ErrorUtils.respondInvalidInput("Cannot remove an inactive punishment.");
ErrorUtils.respondInvalidInput(ctx, "Cannot remove an inactive punishment.");
return;
}
User removedBy = User.byId(ctx.request().getParam("removedBy"));
User removedBy = User.findById(ctx.request().getParam("removedBy"));
if (removedBy == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("removedBy"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("removedBy"));
return;
}
String reason = ctx.request().getParam("reason");
if (reason == null || reason.trim().isEmpty()) {
return ErrorUtils.respondRequiredInput("reason");
ErrorUtils.respondRequiredInput(ctx, "reason");
return;
}
punishment.delete(removedBy, reason);
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of());
return punishment;
APIv3.respondJson(ctx, punishment);
}
}

View File

@ -1,11 +1,14 @@
package net.frozenorb.apiv3.routes.punishments;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Punishment;
public final class GETPunishment implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
return Punishment.byId(ctx.request().getParam("id"));
APIv3.respondJson(ctx, Punishment.findById(ctx.request().getParam("id")));
}
}

View File

@ -1,5 +1,7 @@
package net.frozenorb.apiv3.routes.punishments;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.utils.ErrorUtils;
@ -11,9 +13,9 @@ public final class GETPunishments implements Handler<RoutingContext> {
int limit = ctx.request().getParam("limit") == null ? 100 : Integer.parseInt(ctx.request().getParam("limit"));
int offset = ctx.request().getParam("offset") == null ? 0 : Integer.parseInt(ctx.request().getParam("offset"));
return APIv3.getDatastore().createQuery(Punishment.class).order("addedAt").limit(limit).offset(offset).asList();
APIv3.respondJson(ctx, APIv3.getDatastore().createQuery(Punishment.class).order("addedAt").limit(limit).offset(offset).asList());
} catch (NumberFormatException ex) {
return ErrorUtils.respondInvalidInput("limit and offset must be numerical inputs.");
ErrorUtils.respondInvalidInput(ctx, "limit and offset must be numerical inputs.");
}
}

View File

@ -1,18 +1,23 @@
package net.frozenorb.apiv3.routes.punishments;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserPunishments implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.byId(ctx.request().getParam("id"));
User target = User.findById(ctx.request().getParam("id"));
if (target == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
return target.getPunishments();
APIv3.respondJson(ctx, Punishment.findByUser(target));
}
}

View File

@ -2,6 +2,8 @@ package net.frozenorb.apiv3.routes.punishments;
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.Punishment;
import net.frozenorb.apiv3.models.User;
@ -15,24 +17,27 @@ import java.util.Map;
public final class POSTUserPunish implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.byId(ctx.request().getParam("id"));
User target = User.findById(ctx.request().getParam("id"));
if (target == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
String reason = ctx.request().getParam("reason");
if (reason == null || reason.trim().isEmpty()) {
return ErrorUtils.respondRequiredInput("reason");
ErrorUtils.respondRequiredInput(ctx, "reason");
return;
}
Punishment.PunishmentType type = Punishment.PunishmentType.valueOf(ctx.request().getParam("type"));
if (type != Punishment.PunishmentType.WARN) {
for (Punishment punishment : target.getPunishments(ImmutableSet.of(type))) {
for (Punishment punishment : Punishment.findByUserAndType(target, ImmutableSet.of(type))) {
if (punishment.isActive()) {
return ErrorUtils.error("A punishment by " + User.byId(punishment.getAddedBy()).getLastUsername() + " already covers this user.");
ErrorUtils.respondGeneric(ctx, "A punishment by " + User.findById(punishment.getAddedBy()).getLastUsername() + " already covers this user.");
return;
}
}
}
@ -46,30 +51,33 @@ public final class POSTUserPunish implements Handler<RoutingContext> {
}
if (expiresAt != null && expiresAt.before(new Date())) {
return ErrorUtils.respondInvalidInput("Expiration date cannot be in the past.");
ErrorUtils.respondInvalidInput(ctx, "Expiration date cannot be in the past.");
return;
}
Map<String, Object> meta = Document.parse(req.body());
Map<String, Object> meta = Document.parse(ctx.getBodyAsString());
if (meta == null) {
return ErrorUtils.respondRequiredInput("request body meta");
ErrorUtils.respondRequiredInput(ctx, "request body meta");
return;
}
// We purposely don't do a null check, grants don't have to have a source.
User addedBy = User.byId(ctx.request().getParam("addedBy"));
User addedBy = User.findById(ctx.request().getParam("addedBy"));
if (target.hasPermissionAnywhere(Permissions.PROTECTED_PUNISHMENT)) {
return ErrorUtils.error(target.getLastSeenOn() + " is protected from punishments.");
ErrorUtils.respondGeneric(ctx, target.getLastSeenOn() + " is protected from punishments.");
return;
}
Punishment punishment = new Punishment(target, reason, type, expiresAt, addedBy, ctx.get("actor"), meta);
String accessDenialReason = punishment.getAccessDenialReason();
APIv3.getDatastore().save(punishment);
punishment.insert();
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"punishment", punishment,
"accessDenialReason", accessDenialReason == null ? "" : accessDenialReason
);
));
}
}

View File

@ -1,19 +1,23 @@
package net.frozenorb.apiv3.routes.ranks;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Rank;
import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETERank implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
Rank rank = Rank.byId(ctx.request().getParam("id"));
Rank rank = Rank.findById(ctx.request().getParam("id"));
if (rank == null) {
return ErrorUtils.respondNotFound("Rank", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "Rank", ctx.request().getParam("id"));
return;
}
rank.delete();
return rank;
APIv3.respondJson(ctx, rank);
}
}

View File

@ -1,11 +1,14 @@
package net.frozenorb.apiv3.routes.ranks;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Rank;
public final class GETRank implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
return Rank.byId(ctx.request().getParam("id"));
APIv3.respondJson(ctx, Rank.findById(ctx.request().getParam("id")));
}
}

View File

@ -1,11 +1,14 @@
package net.frozenorb.apiv3.routes.ranks;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Rank;
public final class GETRanks implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
return Rank.values();
APIv3.respondJson(ctx, Rank.findAll());
}
}

View File

@ -1,5 +1,7 @@
package net.frozenorb.apiv3.routes.ranks;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Rank;
@ -14,8 +16,8 @@ public final class POSTRank implements Handler<RoutingContext> {
boolean staffRank = Boolean.parseBoolean(ctx.request().getParam("staffRank"));
Rank rank = new Rank(id, weight, displayName, gameColor, websiteColor, staffRank);
APIv3.getDatastore().save(rank);
return rank;
rank.insert();
APIv3.respondJson(ctx, rank);
}
}

View File

@ -1,19 +1,23 @@
package net.frozenorb.apiv3.routes.serverGroups;
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;
public final class DELETEServerGroup implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
ServerGroup serverGroup = ServerGroup.byId(ctx.request().getParam("id"));
ServerGroup serverGroup = ServerGroup.findById(ctx.request().getParam("id"));
if (serverGroup == null) {
return ErrorUtils.respondNotFound("Server group", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("id"));
return;
}
serverGroup.delete();
return serverGroup;
APIv3.respondJson(ctx, serverGroup);
}
}

View File

@ -1,11 +1,14 @@
package net.frozenorb.apiv3.routes.serverGroups;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.ServerGroup;
public final class GETServerGroup implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
return ServerGroup.byId(ctx.request().getParam("id"));
APIv3.respondJson(ctx, ServerGroup.findById(ctx.request().getParam("id")));
}
}

View File

@ -1,11 +1,14 @@
package net.frozenorb.apiv3.routes.serverGroups;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.ServerGroup;
public final class GETServerGroups implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
return ServerGroup.values();
APIv3.respondJson(ctx, ServerGroup.findAll());
}
}

View File

@ -1,5 +1,7 @@
package net.frozenorb.apiv3.routes.serverGroups;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.ServerGroup;
@ -11,8 +13,8 @@ public final class POSTServerGroup implements Handler<RoutingContext> {
boolean isPublic = Boolean.valueOf(ctx.request().getParam("public"));
ServerGroup serverGroup = new ServerGroup(id, image, isPublic);
APIv3.getDatastore().save(serverGroup);
return serverGroup;
serverGroup.insert();
APIv3.respondJson(ctx, serverGroup);
}
}

View File

@ -1,19 +1,23 @@
package net.frozenorb.apiv3.routes.servers;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETEServer implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
Server server = Server.byId(ctx.request().getParam("id"));
Server server = Server.findById(ctx.request().getParam("id"));
if (server == null) {
return ErrorUtils.respondNotFound("Server", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "Server", ctx.request().getParam("id"));
return;
}
server.delete();
return server;
APIv3.respondJson(ctx, server);
}
}

View File

@ -1,11 +1,14 @@
package net.frozenorb.apiv3.routes.servers;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Server;
public final class GETServer implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
return Server.byId(ctx.request().getParam("id"));
APIv3.respondJson(ctx, Server.findById(ctx.request().getParam("id")));
}
}

View File

@ -1,11 +1,14 @@
package net.frozenorb.apiv3.routes.servers;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Server;
public final class GETServers implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
return Server.values();
APIv3.respondJson(ctx, Server.findAll());
}
}

View File

@ -1,5 +1,7 @@
package net.frozenorb.apiv3.routes.servers;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup;
@ -12,20 +14,22 @@ public final class POSTServer implements Handler<RoutingContext> {
String id = ctx.request().getParam("id");
String displayName = ctx.request().getParam("displayName");
String apiKey = ctx.request().getParam("apiKey");
ServerGroup group = ServerGroup.byId(ctx.request().getParam("group"));
ServerGroup group = ServerGroup.findById(ctx.request().getParam("group"));
String ip = ctx.request().getParam("ip");
if (group == null) {
return ErrorUtils.respondNotFound("Server group", ctx.request().getParam("group"));
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("group"));
return;
}
if (!IPUtils.isValidIP(ip)) {
return ErrorUtils.respondInvalidInput("IP address \"" + ip + "\" is not valid.");
ErrorUtils.respondInvalidInput(ctx, "IP address \"" + ip + "\" is not valid.");
return;
}
Server server = new Server(id, displayName, apiKey, group, ip);
APIv3.getDatastore().save(server);
return server;
server.insert();
APIv3.respondJson(ctx, server);
}
}

View File

@ -1,6 +1,8 @@
package net.frozenorb.apiv3.routes.servers;
import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
@ -21,16 +23,17 @@ public final class POSTServerHeartbeat implements Handler<RoutingContext> {
Actor actor = ctx.get("actor");
if (actor.getType() != ActorType.SERVER) {
return ErrorUtils.respondServerOnly();
ErrorUtils.respondServerOnly(ctx);
return;
}
Server actorServer = Server.byId(actor.getName());
ServerGroup actorServerGroup = ServerGroup.byId(actorServer.getServerGroup());
Document reqJson = Document.parse(req.body());
Document reqJson = Document.parse(ctx.getBodyAsString());
Map<UUID, String> onlinePlayersNames = new HashMap<>();
Map<UUID, User> onlinePlayersUsers = new HashMap<>();
Map<UUID, List<Grant>> onlinePlayersGrants = new HashMap<>();
Map<UUID, List<Punishment>> onlinePlayersPunishments = new HashMap<>();
Map<UUID, User> onlinePlayersUsers;
Map<UUID, List<Grant>> onlinePlayersGrants;
Map<UUID, List<Punishment>> onlinePlayersPunishments;
Map<String, Object> playersResponse = new HashMap<>();
// This code is messy, but we have to do db ops in parallel to avoid
@ -71,7 +74,7 @@ public final class POSTServerHeartbeat implements Handler<RoutingContext> {
UUID uuid = entry.getKey();
User user = entry.getValue();
playersResponse.put(uuid.toString(), user.getLoginInfo(actorServer, onlinePlayersPunishments.get(uuid), onlinePlayersGrants.get(uuid)));
playersResponse.put(uuid.toString(), user.prepareLoginInfo(actorServer, onlinePlayersPunishments.get(uuid), onlinePlayersGrants.get(uuid)));
}
for (Object event : (List<Object>) reqJson.get("events")) {
@ -92,7 +95,7 @@ public final class POSTServerHeartbeat implements Handler<RoutingContext> {
Map<String, Map<String, Boolean>> permissionsResponse = new HashMap<>();
for (Rank rank : Rank.values()) {
for (Rank rank : Rank.findAll()) {
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
PermissionUtils.getDefaultPermissions(rank),
actorServerGroup.calculatePermissions(rank)
@ -104,12 +107,12 @@ public final class POSTServerHeartbeat implements Handler<RoutingContext> {
actorServer.setPlayers(onlinePlayersNames.keySet());
actorServer.setLastTps(reqJson.getDouble("lastTps"));
actorServer.setLastUpdatedAt(new Date());
APIv3.getDatastore().save(actorServer);
actorServer.save();
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"players", playersResponse,
"permissions", permissionsResponse
);
));
}
}

View File

@ -1,29 +1,39 @@
package net.frozenorb.apiv3.routes.users;
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.models.User;
import net.frozenorb.apiv3.models.UserMetaEntry;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
public final class DELETEUserMeta implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.byId(ctx.request().getParam("id"));
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
ServerGroup serverGroup = ServerGroup.byId(ctx.request().getParam("serverGroup"));
ServerGroup serverGroup = ServerGroup.findById(ctx.request().getParam("serverGroup"));
if (serverGroup == null) {
return ErrorUtils.respondNotFound("Server group", ctx.request().getParam("serverGroup"));
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("serverGroup"));
return;
}
UserMetaEntry userMetaEntry = user.getMeta(serverGroup);
UserMetaEntry userMetaEntry = UserMetaEntry.findByUserAndGroup(user, serverGroup);
userMetaEntry.delete();
return userMetaEntry.getData();
if (userMetaEntry != null) {
userMetaEntry.delete();
APIv3.respondJson(ctx, userMetaEntry.getData());
} else {
APIv3.respondJson(ctx, new Document());
}
}
}

View File

@ -2,6 +2,9 @@ package net.frozenorb.apiv3.routes.users;
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.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.models.Punishment;
@ -11,34 +14,38 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETEUserPunishment implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.byId(ctx.request().getParam("id"));
User target = User.findById(ctx.request().getParam("id"));
if (target == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
Punishment.PunishmentType type = Punishment.PunishmentType.valueOf(ctx.request().getParam("type").toUpperCase());
User removedBy = User.byId(ctx.request().getParam("removedBy"));
User removedBy = User.findById(ctx.request().getParam("removedBy"));
if (removedBy == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("removedBy"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("removedBy"));
return;
}
String reason = ctx.request().getParam("reason");
if (reason == null || reason.trim().isEmpty()) {
return ErrorUtils.respondRequiredInput("reason");
ErrorUtils.respondRequiredInput(ctx, "reason");
return;
}
for (Punishment punishment : target.getPunishments(ImmutableSet.of(type))) {
if (punishment.isActive()) {
punishment.delete(removedBy, reason);
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of());
return punishment;
APIv3.respondJson(ctx, punishment);
return;
}
}
return ErrorUtils.error("User provided has no active punishments");
ErrorUtils.respondGeneric(ctx, "User provided has no active punishments");
}
}

View File

@ -1,5 +1,7 @@
package net.frozenorb.apiv3.routes.users;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.Rank;
@ -12,22 +14,22 @@ public final class GETStaff implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
Map<String, Rank> staffRanks = new HashMap<>();
Rank.values().forEach(rank -> {
Rank.findAll().forEach(rank -> {
if (rank.isStaffRank()) {
staffRanks.put(rank.getId(), rank);
}
});
Map<String, Set<User>> result = new TreeMap<>((first, second) -> {
Rank firstRank = Rank.byId(first);
Rank secondRank = Rank.byId(second);
Rank firstRank = staffRanks.get(first);
Rank secondRank = staffRanks.get(second);
return Integer.compare(firstRank.getWeight(), secondRank.getWeight());
});
APIv3.getDatastore().createQuery(Grant.class).field("rank").in(staffRanks.keySet()).forEach(grant -> {
Grant.findByRank(staffRanks.values()).forEach(grant -> {
if (grant.isActive()) {
User user = User.byId(grant.getUser());
User user = User.findById(grant.getUser());
Rank rank = staffRanks.get(grant.getRank());
if (!result.containsKey(rank.getId())) {
@ -38,7 +40,7 @@ public final class GETStaff implements Handler<RoutingContext> {
}
});
return result;
APIv3.respondJson(ctx, result);
}
}

View File

@ -1,11 +1,14 @@
package net.frozenorb.apiv3.routes.users;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
public final class GETUser implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
return User.byId(ctx.request().getParam("id"));
APIv3.respondJson(ctx, User.findById(ctx.request().getParam("id")));
}
}

View File

@ -1,27 +1,35 @@
package net.frozenorb.apiv3.routes.users;
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.models.Grant;
import net.frozenorb.apiv3.models.IPLogEntry;
import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserDetails implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.byId(ctx.request().getParam("id"));
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
// Too many fields to use .of()
return ImmutableMap.builder()
APIv3.respondJson(ctx, ImmutableMap.builder()
.put("user", user)
.put("grants", user.getGrants())
.put("ipLog", user.getIPLog())
.put("punishments", user.getPunishments())
.put("grants", Grant.findByUser(user))
.put("ipLog", IPLogEntry.findByUser(user))
.put("punishments", Punishment.findByUser(user))
.put("aliases", user.getAliases())
.put("totpSetup", user.getTotpSecret() != null)
.build();
.build()
);
}
}

View File

@ -1,5 +1,8 @@
package net.frozenorb.apiv3.routes.users;
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.models.User;
import net.frozenorb.apiv3.models.UserMetaEntry;
@ -8,20 +11,22 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserMeta implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.byId(ctx.request().getParam("id"));
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
ServerGroup serverGroup = ServerGroup.byId(ctx.request().getParam("serverGroup"));
ServerGroup serverGroup = ServerGroup.findById(ctx.request().getParam("serverGroup"));
if (serverGroup == null) {
return ErrorUtils.respondNotFound("Server group", ctx.request().getParam("serverGroup"));
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("serverGroup"));
return;
}
UserMetaEntry userMetaEntry = user.getMeta(serverGroup);
return userMetaEntry.getData();
UserMetaEntry userMetaEntry = UserMetaEntry.findByUserAndGroup(user, serverGroup);
APIv3.respondJson(ctx, userMetaEntry.getData());
}
}

View File

@ -1,6 +1,9 @@
package net.frozenorb.apiv3.routes.users;
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.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IPUtils;
@ -9,36 +12,39 @@ import net.frozenorb.apiv3.utils.TOTPUtils;
public final class GETUserRequiresTOTP implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.byId(ctx.request().getParam("id"));
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
if (user.getTotpSecret() == null) {
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"required", false,
"message", "User does not have TOTP setup."
);
));
return;
}
String userIp = ctx.request().getParam("userIp");
if (!IPUtils.isValidIP(userIp)) {
return ErrorUtils.respondInvalidInput("IP address \"" + userIp + "\" is not valid.");
ErrorUtils.respondInvalidInput(ctx, "IP address \"" + userIp + "\" is not valid.");
return;
}
if (TOTPUtils.isPreAuthorized(user, userIp)) {
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"required", false,
"message", "User's IP has already been validated"
);
));
} else {
APIv3.respondJson(ctx, ImmutableMap.of(
"required", true,
"message", "User has no TOTP exemptions."
));
}
return ImmutableMap.of(
"required", true,
"message", "User has no TOTP exemptions."
);
}
}

View File

@ -1,27 +1,32 @@
package net.frozenorb.apiv3.routes.users;
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.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserVerifyPassword implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.byId(ctx.request().getParam("id"));
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
if (user.getPassword() == null) {
return ErrorUtils.respondInvalidInput("User provided does not have password set.");
ErrorUtils.respondInvalidInput(ctx, "User provided does not have password set.");
return;
}
boolean authorized = user.checkPassword(ctx.request().getParam("password"));
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"authorized", authorized
);
));
}
}

View File

@ -2,6 +2,8 @@ package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableList;
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.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
@ -20,37 +22,42 @@ public final class POSTUserConfirmRegister implements Handler<RoutingContext> {
"nicole chelsea biteme matthew access yankees 987654321 dallas austin thunder taylor matrix").split(" "));
public void handle(RoutingContext ctx) {
User user = User.byEmailToken(ctx.request().getParam("emailToken"));
User user = User.findByEmailToken(ctx.request().getParam("emailToken"));
if (user == null) {
return ErrorUtils.respondNotFound("Email token", ctx.request().getParam("emailToken"));
ErrorUtils.respondNotFound(ctx, "Email token", ctx.request().getParam("emailToken"));
return;
}
// We can't check email != null as that's set while we're pending
// confirmation, we have to check the token.
if (user.getEmailToken() == null) {
return ErrorUtils.error("User provided already has email set.");
ErrorUtils.respondGeneric(ctx, "User provided already has email set.");
return;
}
if ((System.currentTimeMillis() - user.getEmailTokenSetAt().getTime()) > TimeUnit.DAYS.toMillis(2)) {
return ErrorUtils.error("Email token is expired");
ErrorUtils.respondGeneric(ctx, "Email token is expired");
return;
}
String password = ctx.request().getParam("password");
if (password.length() < 8) {
return ErrorUtils.error("Your password is too short.");
ErrorUtils.respondGeneric(ctx, "Your password is too short.");
return;
} else if (commonPasswords.contains(password)) {
return ErrorUtils.error("Your password is too common. Please use a more secure password.");
ErrorUtils.respondGeneric(ctx, "Your password is too common. Please use a more secure password.");
return;
}
user.setEmailToken(null);
user.setPassword(password);
APIv3.getDatastore().save(user);
user.save();
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"success", true
);
));
}
}

View File

@ -1,24 +1,25 @@
package net.frozenorb.apiv3.routes.users;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
public class POSTUserLeave implements Handler<RoutingContext> {
@Override
public Object handle(Request req, Response res) throws Exception {
User user = User.byId(ctx.request().getParam("id"));
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
user.leftServer();
APIv3.getDatastore().save(user);
return user;
user.save();
APIv3.respondJson(ctx, user);
}
}
}

View File

@ -1,5 +1,8 @@
package net.frozenorb.apiv3.routes.users;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.models.Server;
@ -16,20 +19,23 @@ public final class POSTUserLogin implements Handler<RoutingContext> {
UUID uuid = UUID.fromString(ctx.request().getParam("id"));
if (!UUIDUtils.isAcceptableUUID(uuid)) {
return ErrorUtils.respondInvalidInput("UUID \"" + uuid + "\" is not valid - must be version 4 UUID.");
ErrorUtils.respondInvalidInput(ctx, "UUID \"" + uuid + "\" is not valid - must be version 4 UUID.");
return;
}
User user = User.byId(uuid);
User user = User.findById(uuid);
String username = ctx.request().getParam("username");
String userIp = ctx.request().getParam("userIp");
Actor actor = ctx.get("actor");
if (actor.getType() != ActorType.SERVER) {
return ErrorUtils.respondServerOnly();
ErrorUtils.respondServerOnly(ctx);
return;
}
if (!IPUtils.isValidIP(userIp)) {
return ErrorUtils.respondInvalidInput("IP address \"" + userIp + "\" is not valid.");
ErrorUtils.respondInvalidInput(ctx, "IP address \"" + userIp + "\" is not valid.");
return;
}
if (user == null) {
@ -37,11 +43,11 @@ public final class POSTUserLogin implements Handler<RoutingContext> {
user = new User(uuid, username);
}
Server actorServer = Server.byId(actor.getName());
Server actorServer = Server.findById(actor.getName());
user.getIPLogEntry(userIp).used();
user.updateUsername(username);
return user.getLoginInfo(actorServer);
APIv3.respondJson(ctx, user.getLoginInfo(actorServer));
}
}

View File

@ -1,6 +1,9 @@
package net.frozenorb.apiv3.routes.users;
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.models.NotificationTemplate;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Notification;
@ -12,43 +15,47 @@ import java.util.Map;
public final class POSTUserNotify implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.byId(ctx.request().getParam("id"));
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
if (user.getEmail() == null) {
return ErrorUtils.error("User provided does not have email set.");
ErrorUtils.respondGeneric(ctx, "User provided does not have email set.");
return;
}
NotificationTemplate template = NotificationTemplate.byId(ctx.request().getParam("template"));
NotificationTemplate template = NotificationTemplate.findById(ctx.request().getParam("template"));
if (template == null) {
return ErrorUtils.respondNotFound("Notification template", ctx.request().getParam("template"));
ErrorUtils.respondNotFound(ctx, "Notification template", ctx.request().getParam("template"));
return;
}
Map<String, Object> subjectReplacements = new HashMap<>();
Map<String, Object> bodyReplacements = new HashMap<>();
req.queryMap("subject").toMap().forEach((key, values) -> {
//TODO: Probably make this use the body as json
/*req.queryMap("subject").toMap().forEach((key, values) -> {
subjectReplacements.put(key, values[0]);
});
req.queryMap("body").toMap().forEach((key, values) -> {
bodyReplacements.put(key, values[0]);
});
});*/
try {
Notification notification = new Notification(template, subjectReplacements, bodyReplacements);
notification.sendAsEmail(user.getEmail());
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"success", true
);
));
} catch (Exception ex) {
ex.printStackTrace();
return ErrorUtils.error("Failed to send notification");
ErrorUtils.respondGeneric(ctx, "Failed to send notification");
}
}

View File

@ -1,6 +1,8 @@
package net.frozenorb.apiv3.routes.users;
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.models.NotificationTemplate;
import net.frozenorb.apiv3.models.User;
@ -22,30 +24,34 @@ public final class POSTUserRegister implements Handler<RoutingContext> {
);
public void handle(RoutingContext ctx) {
User user = User.byId(ctx.request().getParam("id"));
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
if (user.getEmail() != null) {
return ErrorUtils.respondInvalidInput("User provided already has email set.");
ErrorUtils.respondInvalidInput(ctx, "User provided already has email set.");
return;
}
String email = ctx.request().getParam("email");
if (!VALID_EMAIL_PATTERN.matcher(email).matches()) {
return ErrorUtils.error(email + " is not a valid email.");
ErrorUtils.respondGeneric(ctx, email + " is not a valid email.");
return;
}
if (user.getEmailToken() != null && (System.currentTimeMillis() - user.getEmailTokenSetAt().getTime()) < TimeUnit.DAYS.toMillis(2)) {
return ErrorUtils.error("We just recently sent you a confirmation email. Please wait before trying to register again.");
ErrorUtils.respondGeneric(ctx, "We just recently sent you a confirmation email. Please wait before trying to register again.");
return;
}
user.setEmail(email);
user.setEmailToken(new BigInteger(130, new Random()).toString(32));
user.setEmailTokenSetAt(new Date());
APIv3.getDatastore().save(user);
user.save();
Map<String, Object> replacements = ImmutableMap.of(
"username", user.getLastUsername(),
@ -53,16 +59,16 @@ public final class POSTUserRegister implements Handler<RoutingContext> {
"emailToken", user.getEmailToken()
);
Notification notification = new Notification(NotificationTemplate.byId("email-confirmation"), replacements, replacements);
Notification notification = new Notification(NotificationTemplate.findById("email-confirmation"), replacements, replacements);
try {
notification.sendAsEmail(user.getEmail());
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"success", true
);
));
} catch (Exception ex) {
ex.printStackTrace();
return ErrorUtils.error("Failed to send confirmation email. Please contact a MineHQ staff member.");
ErrorUtils.respondGeneric(ctx, "Failed to send confirmation email. Please contact a MineHQ staff member.");
}
}

View File

@ -2,6 +2,8 @@ package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableMap;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
@ -10,24 +12,26 @@ import net.frozenorb.apiv3.utils.TOTPUtils;
public final class POSTUserSetupTOTP implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.byId(ctx.request().getParam("id"));
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
if (user.getTotpSecret() != null) {
return ErrorUtils.respondInvalidInput("User provided already has TOTP code set.");
ErrorUtils.respondInvalidInput(ctx, "User provided already has TOTP code set.");
return;
}
GoogleAuthenticatorKey generated = TOTPUtils.generateTOTPKey();
user.setTotpSecret(generated.getKey());
APIv3.getDatastore().save(user);
user.save();
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"qrCode", TOTPUtils.getQRCodeURL(user, generated)
);
));
}
}

View File

@ -1,6 +1,9 @@
package net.frozenorb.apiv3.routes.users;
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.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IPUtils;
@ -11,29 +14,33 @@ import java.util.concurrent.TimeUnit;
public final class POSTUserVerifyTOTP implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.byId(ctx.request().getParam("id"));
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
if (user.getTotpSecret() == null) {
return ErrorUtils.respondInvalidInput("User provided does not have TOTP code set.");
ErrorUtils.respondInvalidInput(ctx, "User provided does not have TOTP code set.");
return;
}
String userIp = ctx.request().getParam("userIp");
if (!IPUtils.isValidIP(userIp)) {
return ErrorUtils.respondInvalidInput("IP address \"" + userIp + "\" is not valid.");
ErrorUtils.respondInvalidInput(ctx, "IP address \"" + userIp + "\" is not valid.");
return;
}
int providedCode = Integer.parseInt(ctx.request().getParam("code"));
if (TOTPUtils.wasRecentlyUsed(user, providedCode)) {
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"authorized", false,
"message", "TOTP code was recently used."
);
));
return;
}
boolean authorized = TOTPUtils.authorizeUser(user, providedCode);
@ -42,15 +49,15 @@ public final class POSTUserVerifyTOTP implements Handler<RoutingContext> {
TOTPUtils.markPreAuthorized(user, userIp, 3, TimeUnit.DAYS);
TOTPUtils.markRecentlyUsed(user, providedCode);
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"authorized", true,
"message", "Valid TOTP code provided."
);
));
} else {
return ImmutableMap.of(
APIv3.respondJson(ctx, ImmutableMap.of(
"authorized", false,
"message", "TOTP code was not valid."
);
));
}
}

View File

@ -1,5 +1,8 @@
package net.frozenorb.apiv3.routes.users;
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.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
@ -8,22 +11,24 @@ import org.bson.Document;
public final class PUTUserMeta implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.byId(ctx.request().getParam("id"));
User user = User.findById(ctx.request().getParam("id"));
if (user == null) {
return ErrorUtils.respondNotFound("User", ctx.request().getParam("id"));
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
ServerGroup serverGroup = ServerGroup.byId(ctx.request().getParam("serverGroup"));
ServerGroup serverGroup = ServerGroup.findById(ctx.request().getParam("serverGroup"));
if (serverGroup == null) {
return ErrorUtils.respondNotFound("Server group", ctx.request().getParam("serverGroup"));
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("serverGroup"));
return;
}
Document data = Document.parse(req.body());
Document data = Document.parse(ctx.getBodyAsString());
user.saveMeta(serverGroup, data);
return data;
APIv3.respondJson(ctx, data);
}
}

View File

@ -0,0 +1,29 @@
package net.frozenorb.apiv3.unsorted;
import com.google.common.util.concurrent.SettableFuture;
import com.mongodb.async.SingleResultCallback;
import java.util.concurrent.ExecutionException;
public final class BlockingCallback<T> implements SingleResultCallback<T> {
private final SettableFuture<T> future = SettableFuture.create();
public void onResult(T val, Throwable error) {
if (error != null) {
future.setException(error);
} else {
future.set(val);
}
}
public T get() {
try {
return future.get();
} catch (InterruptedException | ExecutionException ex) {
// No matter what we get we'll just rethrow.
throw new RuntimeException(ex);
}
}
}

View File

@ -1,27 +0,0 @@
package net.frozenorb.apiv3.unsorted;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.types.ObjectId;
import spark.ExceptionHandler;
import spark.Request;
import spark.Response;
@Slf4j
public final class LoggingExceptionHandler implements ExceptionHandler {
public void handle(Exception ex, Request req, Response res) {
long started = req.attribute("requestStarted");
APIv3.getStatsD().recordExecutionTime("apiv3.http.executionTime", System.currentTimeMillis() - started);
APIv3.getStatsD().incrementCounter("apiv3.http.requests");
APIv3.getStatsD().incrementCounter("apiv3.http.errors");
String code = new ObjectId().toHexString();
log.error(code + ":", ex);
res.body(APIv3.getGson().toJson(ErrorUtils.error("An unknown error has occurred. Please contact an API developer with the code \"" + code + "\".")));
}
}

View File

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

View File

@ -1,10 +1,9 @@
package net.frozenorb.apiv3.utils;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import org.bson.Document;
import java.util.UUID;
@ -12,30 +11,32 @@ import java.util.UUID;
@UtilityClass
public class MojangUtils {
private static OkHttpClient okHttpClient = new OkHttpClient.Builder().retryOnConnectionFailure(false).build();
public static String getName(UUID id) {
Request.Builder builder = new Request.Builder();
BlockingCallback<String> callback = new BlockingCallback<>();
getName(id, callback);
return callback.get();
}
builder.get();
builder.url("https://sessionserver.mojang.com/session/minecraft/profile/" + id.toString().replace("-", ""));
public static void getName(UUID id, SingleResultCallback<String> callback) {
APIv3.getHttpClient().get("sessionserver.mojang.com", "session/minecraft/profile/" + id.toString().replace("-", ""), (response) -> {
response.bodyHandler((body) -> {
Document resJson = Document.parse(body.toString());
String name = resJson.getString("name");
try {
Response response = okHttpClient.newCall(builder.build()).execute();
Document resJson = Document.parse(response.body().string());
if (name == null) {
APIv3.getStatsD().incrementCounter("apiv3.mojang.sessionServer.rateLimited");
callback.onResult(null, new RuntimeException("Hit Mojang API rate limit: " + resJson.toJson()));
} else {
APIv3.getStatsD().incrementCounter("apiv3.mojang.sessionServer.success");
callback.onResult(name, null);
}
});
String name = resJson.getString("name");
if (name == null) {
throw new RuntimeException("Hit Mojang API rate limit: " + resJson.toJson());
}
APIv3.getStatsD().incrementCounter("apiv3.mojang.sessionServer.success");
return name;
} catch (Exception ex) {
APIv3.getStatsD().incrementCounter("apiv3.mojang.sessionServer.failure");
throw new RuntimeException(ex);
}
response.exceptionHandler((error) -> {
APIv3.getStatsD().incrementCounter("apiv3.mojang.sessionServer.failure");
callback.onResult(null, error);
});
});
}
}

View File

@ -25,7 +25,7 @@ public class PermissionUtils {
public static Map<String, Boolean> mergeUpTo(Map<String, List<String>> unmerged, Rank upTo) {
Map<String, Boolean> result = new HashMap<>();
for (Rank rank : Rank.values()) {
for (Rank rank : Rank.findAll()) {
Map<String, Boolean> rankPermissions = convertToMap(unmerged.get(rank.getId()));
// If there's no permissions defined for this rank just skip it.

View File

@ -0,0 +1,27 @@
package net.frozenorb.apiv3.utils;
import com.mongodb.async.client.MongoIterable;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import java.util.ArrayList;
import java.util.List;
@UtilityClass
public class SyncUtils {
public static <T> T blockOne(MongoIterable<T> mongoIterable) {
BlockingCallback<T> callback = new BlockingCallback<>();
mongoIterable.first(callback);
return callback.get();
}
public static <T> List<T> blockMulti(MongoIterable<T> mongoIterable) {
BlockingCallback<List<T>> callback = new BlockingCallback<>();
mongoIterable.into(new ArrayList<>(), callback);
return callback.get();
}
}