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

This commit is contained in:
Colin McDonald 2016-06-12 16:36:11 -04:00
parent 60a622fc43
commit 7e462e1915
65 changed files with 1026 additions and 520 deletions

View File

@ -1,6 +1,4 @@
general.releaseStage=production
logging.level=info
logging.debug=true
mongo.address=localhost
mongo.port=27017
mongo.database=MineHQ
@ -13,6 +11,5 @@ twillio.accountSID=AC9e2f88c5690134d29a56f698de3cd740
twillio.authToken=982592505a171d3be6b0722f5ecacc0e
mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ
bugsnag.apiKey=0e47fba8b825416b7cbc839066184509
auth.permittedUserRanks=developer,owner
auth.websiteApiKey=RVbp4hY6sCFVaf
auth.bungeeCordApiKey=6d9cf76dc9f0d23

27
pom.xml
View File

@ -55,6 +55,10 @@
<id>minehq-repo</id>
<url>http://maven.minehq.com:8081/artifactory/minehq-all/</url>
</repository>
<repository>
<id>mongo-jackson-codec-repo</id>
<url>https://dl.bintray.com/ylemoigne/maven</url>
</repository>
</repositories>
<dependencies>
@ -62,12 +66,17 @@
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>LATEST</version>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>LATEST</version>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-redis-client</artifactId>
<version>3.2.1</version>
</dependency>
<!-- Google Libs -->
@ -89,16 +98,14 @@
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>eu.dozd</groupId>
<artifactId>mongo-mapper</artifactId>
<version>1.0.1</version>
<groupId>fr.javatic.mongo</groupId>
<artifactId>mongo-jackson-codec</artifactId>
<version>3.2.0__0.4</version>
</dependency>
<!-- Redis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
<groupId>de.undercouch</groupId>
<artifactId>bson4jackson</artifactId>
<version>2.6.0</version>
</dependency>
<!-- Notifications -->

View File

@ -1,39 +1,50 @@
package net.frozenorb.apiv3;
import com.bugsnag.Client;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.google.common.collect.ImmutableList;
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.Block;
import com.mongodb.ConnectionString;
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.ClusterConnectionMode;
import com.mongodb.connection.ClusterSettings;
import com.timgroup.statsd.NonBlockingStatsDClient;
import com.timgroup.statsd.StatsDClient;
import eu.dozd.mongo.MongoMapper;
import fr.javatic.mongo.jacksonCodec.JacksonCodecProvider;
import fr.javatic.mongo.jacksonCodec.ObjectMapperFactory;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Handler;
import io.vertx.core.Vertx;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.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 io.vertx.ext.web.impl.BlockingHandlerDecorator;
import io.vertx.ext.web.impl.RouteImpl;
import io.vertx.redis.RedisClient;
import io.vertx.redis.RedisOptions;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.actors.ActorType;
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.handlers.ActorAttributeHandler;
import net.frozenorb.apiv3.handlers.AuthorizationHandler;
import net.frozenorb.apiv3.handlers.MetricsHandler;
import net.frozenorb.apiv3.models.*;
import net.frozenorb.apiv3.routes.GETDump;
import net.frozenorb.apiv3.routes.GETWhoAmI;
import net.frozenorb.apiv3.routes.POSTMetrics;
@ -57,19 +68,23 @@ import net.frozenorb.apiv3.routes.serverGroups.GETServerGroups;
import net.frozenorb.apiv3.routes.serverGroups.POSTServerGroup;
import net.frozenorb.apiv3.routes.servers.*;
import net.frozenorb.apiv3.routes.users.*;
import net.frozenorb.apiv3.serialization.DateTypeAdapter;
import net.frozenorb.apiv3.serialization.FollowAnnotationExclusionStrategy;
import net.frozenorb.apiv3.serialization.ObjectIdTypeAdapter;
import net.frozenorb.apiv3.serialization.*;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.unsorted.BugsnagSLF4JLogger;
import net.frozenorb.apiv3.utils.IPUtils;
import net.frozenorb.apiv3.utils.SyncUtils;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.Document;
import org.bson.codecs.BsonValueCodecProvider;
import org.bson.codecs.DocumentCodecProvider;
import org.bson.codecs.ValueCodecProvider;
import org.bson.codecs.configuration.CodecProvider;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.types.ObjectId;
import redis.clients.jedis.JedisPool;
import java.io.FileInputStream;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@ -80,26 +95,27 @@ public final class APIv3 extends AbstractVerticle {
@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 RedisClient redisClient;
@Getter private static StatsDClient statsD;
@Getter private static Vertx vertxInstance;
@Getter private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(ObjectId.class, new ObjectIdTypeAdapter())
.registerTypeAdapter(Date.class, new DateTypeAdapter())
.setExclusionStrategies(new FollowAnnotationExclusionStrategy())
.create();
@Override
public void start() {
setupConfig();
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", config.getProperty("logging.level"));
vertxInstance = vertx;
setupConfig();
setupDatabase();
setupRedis();
setupMetrics();
setupBugsnag();
setupHttpServer();
//setupHttpServer();
setupHttpClient();
//convertData("158.69.126.126", true);
convertData("mongodb://158.69.126.126", true);
}
private void setupConfig() {
@ -125,18 +141,37 @@ public final class APIv3 extends AbstractVerticle {
ClusterSettings clusterSettings = ClusterSettings
.builder()
.hosts(ImmutableList.of(
new ServerAddress(
config.getProperty("mongo.address"),
Integer.parseInt(config.getProperty("mongo.port"))
)
))
.applyConnectionString(new ConnectionString("mongodb://" + config.getProperty("mongo.address") + ":" + config.getProperty("mongo.port")))
.build();
List<CodecProvider> providers = new ArrayList<>();
// Our override codec
providers.add(new MineHQCodecProvider());
// Normal providers
providers.add(new ValueCodecProvider());
providers.add(new DocumentCodecProvider());
providers.add(new BsonValueCodecProvider());
ObjectMapper objectMapper = ObjectMapperFactory.createObjectMapper();
SimpleModule simpleModule = new SimpleModule();
simpleModule.addSerializer(UUID.class, new UUIDJsonSerializer());
simpleModule.addDeserializer(UUID.class, new UUIDJsonDeserializer());
objectMapper.registerModule(simpleModule);
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE);
objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
providers.add(new JacksonCodecProvider(objectMapper));
MongoClientSettings settings = MongoClientSettings
.builder()
.codecRegistry(CodecRegistries.fromProviders(MongoMapper.getProviders()))
.codecRegistry(CodecRegistries.fromProviders(providers))
.credentialList(credentials)
.clusterSettings(clusterSettings).build();
.clusterSettings(clusterSettings)
.build();
MongoClient client = MongoClients.create(settings);
database = client.getDatabase(config.getProperty("mongo.database"));
@ -144,9 +179,11 @@ public final class APIv3 extends AbstractVerticle {
}
private void setupRedis() {
redisPool = new JedisPool(
config.getProperty("redis.address"),
Integer.parseInt(config.getProperty("redis.port"))
redisClient = RedisClient.create(
vertx,
new RedisOptions()
.setAddress(config.getProperty("redis.address"))
.setPort(Integer.parseInt(config.getProperty("redis.port")))
);
}
@ -176,8 +213,8 @@ public final class APIv3 extends AbstractVerticle {
Router mainRouter = Router.router(vertx);
mainRouter.route().handler(new MetricsHandler());
mainRouter.route().handler(new ActorAttributeFilter());
mainRouter.route().handler(new AuthorizationFilter());
mainRouter.route().handler(new ActorAttributeHandler());
mainRouter.route().handler(new AuthorizationHandler());
mainRouter.route().handler(LoggerHandler.create());
mainRouter.route().handler(BodyHandler.create());
@ -218,7 +255,7 @@ public final class APIv3 extends AbstractVerticle {
mainRouter.get("/server/:id").blockingHandler(new GETServer());
mainRouter.get("/servers").blockingHandler(new GETServers());
mainRouter.post("/server/heartbeat").blockingHandler(new POSTServerHeartbeat());
mainRouter.post("/server/heartbeat").handler(new POSTServerHeartbeat());
mainRouter.post("/server").blockingHandler(new POSTServer());
//put("/server/:id").blockingHandler(new PUTServer());
mainRouter.delete("/server/:id").blockingHandler(new DELETEServer());
@ -246,7 +283,22 @@ public final class APIv3 extends AbstractVerticle {
mainRouter.delete("/user/:id/punishment").blockingHandler(new DELETEUserPunishment());
mainRouter.getRoutes().forEach((route) -> {
System.out.println(route.getClass() + "||" + route.getPath());
try {
RouteImpl impl = (RouteImpl) route;
Field field = RouteImpl.class.getDeclaredField("contextHandler");
field.setAccessible(true);
Handler<RoutingContext> handler = (Handler<RoutingContext>) field.get(impl);
if (handler instanceof BlockingHandlerDecorator) {
field = BlockingHandlerDecorator.class.getDeclaredField("decoratedHandler");
field.setAccessible(true);
handler = (Handler<RoutingContext>) field.get(handler);
}
System.out.println(route.getPath() + " is handled by " + handler.getClass());
} catch (Exception ex) {
}
});
int port = Integer.parseInt(config.getProperty("http.port"));
@ -276,174 +328,156 @@ public final class APIv3 extends AbstractVerticle {
AtomicInteger skippedGrants = new AtomicInteger();
AtomicInteger skippedIpLogs = new AtomicInteger();
importFrom.getCollection("user").find().forEach(new Block<Document>() {
SyncUtils.blockMulti(importFrom.getCollection("user").find()).forEach((user) -> {
String uuidString = String.valueOf(user.get("uuid"));
@Override
public void apply(Document user) {
String uuidString = String.valueOf(user.get("uuid"));
if (uuidString == null || uuidString.length() != 32) {
return;
}
UUID uuid = UUID.fromString(uuidString.replaceFirst( "([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]+)", "$1-$2-$3-$4-$5" ));
if (!UUIDUtils.isAcceptableUUID(uuid)) {
skippedUsers.incrementAndGet();
return;
}
mongoIdToUUID.put(user.getObjectId("_id"), uuid);
User created = new User(
uuid,
String.valueOf(user.get("name")).toString(),
ImmutableMap.of(),
null,
null,
null,
null,
user.getString("email"),
user.getString("phone"),
"INVALID",
user.getDate("joined"),
user.getDate("joined"),
false
);
if (forReal) {
created.insert();
}
log.info("Created user " + created.getLastUsername() + " (" + created.getId() + ")");
if (uuidString == null || uuidString.length() != 32) {
return;
}
}, (a, b) -> {});
importFrom.getCollection("punishment").find().forEach(new Block<Document>() {
UUID uuid = UUID.fromString(uuidString.replaceFirst("([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]+)", "$1-$2-$3-$4-$5"));
@Override
public void apply(Document punishment) {
UUID target = mongoIdToUUID.get(((DBRef) punishment.get("user")).getId());
if (target == null) {
skippedPunishments.incrementAndGet();
return;
}
// Old punishments have this value set to false to indicate they're not active anymore.
if (punishment.containsKey("active") && !punishment.getBoolean("active")) {
return;
}
com.mongodb.
Punishment created = new Punishment(
new ObjectId().toString(),
target,
punishment.getString("reason").toString(),
Punishment.PunishmentType.valueOf(punishment.getString("type").toUpperCase()),
punishment.getDate("expires"),
punishment.containsKey("meta") ? (punishment.get("meta") instanceof List ? ImmutableMap.of() : (Document) punishment.get("meta")) : ImmutableMap.of(),
punishment.containsKey("addedBy") ? mongoIdToUUID.get(((DBRef) punishment.get("addedBy")).getId()) : null,
(Date) punishment.getDate("created").clone(),
punishment.containsKey("createdOn") ? String.valueOf(((DBRef) punishment.get("createdOn")).getId()) : "Website",
punishment.containsKey("createdOn") ? ActorType.SERVER : ActorType.WEBSITE,
punishment.containsKey("removedBy") ? (((DBRef) punishment.get("removedBy")).getCollectionName().equals("user") ? mongoIdToUUID.get(((DBRef) punishment.get("removedBy")).getId()) : null) : null,
punishment.getDate("created"),
punishment.containsKey("removalReason") ? punishment.getString("removalReason").toString() : ""
);
if (forReal) {
created.insert();
}
log.info("Created punishment " + created.getId() + " (" + created.getType() + ")");
if (!UUIDUtils.isAcceptableUUID(uuid)) {
skippedUsers.incrementAndGet();
return;
}
}, (a, b) -> {});
importFrom.getCollection("grant").find().forEach(new Block<Document>() {
mongoIdToUUID.put(user.getObjectId("_id"), uuid);
@Override
public void apply(Document grant) {
UUID target = mongoIdToUUID.get(((DBRef) grant.get("target")).getId());
User created = new User(
uuid,
String.valueOf(user.get("name")).toString(),
ImmutableMap.of(user.getString("name"), user.getDate("joined")),
null,
null,
null,
null,
user.getString("email"),
user.getString("phone"),
"INVALID",
user.getDate("joined"),
user.getDate("joined"),
false
);
if (target == null) {
skippedGrants.incrementAndGet();
return;
}
String rank = grant.getString("role");
if (rank.equalsIgnoreCase("unban") || rank.equalsIgnoreCase("pass") || rank.equalsIgnoreCase("pink") || rank.equalsIgnoreCase("jrdev")) {
return;
} else if (rank.equalsIgnoreCase("high_roller")) {
rank = "high-roller";
} else if (rank.equalsIgnoreCase("dev")) {
rank = "developer";
}
Grant created = new Grant(
new ObjectId().toString(),
target,
grant.containsKey("comment") ? grant.getString("comment") : "",
grant.containsKey("scope") ? ImmutableSet.copyOf((Collection<String>) grant.get("scope")) : ImmutableSet.of(),
rank,
grant.getDate("expires"),
grant.containsKey("addedBy") ? mongoIdToUUID.get(((DBRef) grant.get("addedBy")).getId()) : null,
grant.containsKey("created") ? grant.getDate("created") : new Date(),
null,
null,
null
);
if (forReal) {
created.insert();
}
log.info("Created grant " + created.getId() + " (" + created.getRank() + ")");
if (forReal) {
created.insert();
}
}, (a, b) -> {});
importFrom.getCollection("iplog").find().forEach(new Block<Document>() {
log.info("Created user " + created.getLastUsername() + " (" + created.getId() + ")");
});
@Override
public void apply(Document ipLogEntry) {
UUID user = mongoIdToUUID.get(((DBRef) ipLogEntry.get("user")).getId());
SyncUtils.blockMulti(importFrom.getCollection("punishment").find()).forEach((punishment) -> {
UUID target = mongoIdToUUID.get(((Map<String, Object>) punishment.get("user")).get("$id"));
if (user == null || ipLogEntry.getString("ip") == null) {
skippedIpLogs.incrementAndGet();
return;
}
String ip = ipLogEntry.getString("ip").replace("/", "");
if (!IPUtils.isValidIP(ip)) {
return;
}
Date lastSeen = ipLogEntry.getDate("lastSeen");
if (lastSeen == null) {
lastSeen = new Date();
}
IPLogEntry created = new IPLogEntry(
new ObjectId().toString(),
user,
ip,
lastSeen,
lastSeen,
((Number) ipLogEntry.get("uses")).intValue()
);
if (forReal) {
created.insert();
}
log.info("Created ip log entry " + created.getId() + " (" + created.getUser() + " - " + created.getUserIp() + ")");
if (target == null) {
skippedPunishments.incrementAndGet();
return;
}
}, (a, b) -> {});
// Old punishments have this value set to false to indicate they're not active anymore.
if (punishment.containsKey("active") && !punishment.getBoolean("active")) {
return;
}
Punishment created = new Punishment(
new ObjectId().toString(),
target,
punishment.getString("reason").toString(),
Punishment.PunishmentType.valueOf(punishment.getString("type").toUpperCase()),
punishment.getDate("expires"),
punishment.containsKey("meta") ? (punishment.get("meta") instanceof List ? ImmutableMap.of() : (Document) punishment.get("meta")) : ImmutableMap.of(),
punishment.containsKey("addedBy") ? mongoIdToUUID.get(((Map<String, Object>) punishment.get("addedBy")).get("$id")) : null,
(Date) punishment.getDate("created").clone(),
punishment.containsKey("createdOn") ? String.valueOf(((Map<String, Object>) punishment.get("createdOn")).get("$id")) : "Website",
punishment.containsKey("createdOn") ? ActorType.SERVER : ActorType.WEBSITE,
punishment.containsKey("removedBy") ? (((Map<String, Object>) punishment.get("removedBy")).get("$ref").equals("user") ? mongoIdToUUID.get(((Map<String, Object>) punishment.get("removedBy")).get("$id")) : null) : null,
punishment.getDate("created"),
punishment.containsKey("removalReason") ? punishment.getString("removalReason").toString() : ""
);
if (forReal) {
created.insert();
}
log.info("Created punishment " + created.getId() + " (" + created.getType() + ")");
});
SyncUtils.blockMulti(importFrom.getCollection("grant").find()).forEach((grant) -> {
UUID target = mongoIdToUUID.get(((Map<String, Object>) grant.get("target")).get("$id"));
if (target == null) {
skippedGrants.incrementAndGet();
return;
}
String rank = grant.getString("role");
if (rank.equalsIgnoreCase("unban") || rank.equalsIgnoreCase("pass") || rank.equalsIgnoreCase("pink") || rank.equalsIgnoreCase("jrdev")) {
return;
} else if (rank.equalsIgnoreCase("high_roller")) {
rank = "high-roller";
} else if (rank.equalsIgnoreCase("dev")) {
rank = "developer";
}
Grant created = new Grant(
new ObjectId().toString(),
target,
grant.containsKey("comment") ? grant.getString("comment") : "",
grant.containsKey("scope") ? ImmutableSet.copyOf((Collection<String>) grant.get("scope")) : ImmutableSet.of(),
rank,
grant.getDate("expires"),
grant.containsKey("addedBy") ? mongoIdToUUID.get(((Map<String, Object>) grant.get("addedBy")).get("$id")) : null,
grant.containsKey("created") ? grant.getDate("created") : new Date(),
null,
null,
null
);
if (forReal) {
created.insert();
}
log.info("Created grant " + created.getId() + " (" + created.getRank() + ")");
});
SyncUtils.blockMulti(importFrom.getCollection("iplog").find()).forEach((ipLogEntry) -> {
UUID user = mongoIdToUUID.get(((Map<String, Object>) ipLogEntry.get("user")).get("$id"));
if (user == null || ipLogEntry.getString("ip") == null) {
skippedIpLogs.incrementAndGet();
return;
}
String ip = ipLogEntry.getString("ip").replace("/", "");
if (!IPUtils.isValidIP(ip)) {
return;
}
Date lastSeen = ipLogEntry.getDate("lastSeen");
if (lastSeen == null) {
lastSeen = new Date();
}
IPLogEntry created = new IPLogEntry(
new ObjectId().toString(),
user,
ip,
lastSeen,
lastSeen,
((Number) ipLogEntry.get("uses")).intValue()
);
if (forReal) {
created.insert();
}
log.info("Created ip log entry " + created.getId() + " (" + created.getUser() + " - " + created.getUserIp() + ")");
});
log.info("Skipped " + skippedUsers.get() + " users, " + skippedPunishments.get() + " punishments, " + skippedGrants.get() + " grants, and " + skippedIpLogs.get() + " ip logs");
}

View File

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

View File

@ -2,14 +2,17 @@ package net.frozenorb.apiv3.actors;
public final class BungeeCordActor implements Actor {
@Override
public boolean isAuthorized() {
return true;
}
@Override
public String getName() {
return "BungeeCord";
}
@Override
public ActorType getType() {
return ActorType.BUNGEECORD;
}

View File

@ -1,5 +1,6 @@
package net.frozenorb.apiv3.actors;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.models.Server;
@ -11,14 +12,17 @@ public final class ServerActor implements Actor {
this.server = server;
}
@Override
public boolean isAuthorized() {
return true;
}
@Override
public String getName() {
return server.getId();
}
@Override
public ActorType getType() {
return ActorType.SERVER;
}

View File

@ -2,14 +2,17 @@ package net.frozenorb.apiv3.actors;
public final class UnknownActor implements Actor {
@Override
public boolean isAuthorized() {
return false;
}
@Override
public String getName() {
return "Unknown";
}
@Override
public ActorType getType() {
return ActorType.UNKNOWN;
}

View File

@ -4,35 +4,40 @@ import com.google.common.collect.ImmutableSet;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Permissions;
import java.util.Set;
public final class UserActor implements Actor {
private static final Set<String> permittedUserRanks = ImmutableSet.copyOf(APIv3.getConfig().getProperty("auth.permittedUserRanks").toLowerCase().split(","));
@Getter private final User user;
// We use Boolean here so we can have null = not calculated;
// Currently having this cached isn't important as we only check
// this once, but later on when we have non-logged in routes
// this will be important.
private Boolean cachedAuthorized = null;
public UserActor(User user) {
this.user = user;
}
@Override
public boolean isAuthorized() {
if (cachedAuthorized != null) {
return cachedAuthorized;
} else {
String highestRankId = user.getHighestRankAnywhere().getId();
cachedAuthorized = permittedUserRanks.contains(highestRankId.toLowerCase());
return cachedAuthorized;
boolean authorized = user.hasPermissionAnywhere(Permissions.SIGN_API_REQUEST);
cachedAuthorized = authorized;
return authorized;
}
}
@Override
public String getName() {
return user.getLastUsername();
}
@Override
public ActorType getType() {
return ActorType.USER;
}

View File

@ -2,14 +2,17 @@ package net.frozenorb.apiv3.actors;
public final class WebsiteActor implements Actor {
@Override
public boolean isAuthorized() {
return true;
}
@Override
public String getName() {
return "Website";
}
@Override
public ActorType getType() {
return ActorType.WEBSITE;
}

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.filters;
package net.frozenorb.apiv3.handlers;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
@ -10,41 +10,46 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
import java.util.Base64;
public final class ActorAttributeFilter implements Handler<RoutingContext> {
public final class ActorAttributeHandler implements Handler<RoutingContext> {
// TODO: MAKE THIS ASYNC
@Override
public void handle(RoutingContext ctx) {
String authHeader = ctx.request().getHeader("Authorization");
String mhqAuthHeader = ctx.request().getHeader("MHQ-Authorization");
String authorizationHeader = ctx.request().getHeader("Authorization");
String mhqAuthorizationHeader = ctx.request().getHeader("MHQ-Authorization");
if (authHeader != null) {
processBasicAuthorization(authHeader, ctx);
} else if (mhqAuthHeader != null) {
processMHQAuthorization(mhqAuthHeader, ctx);
if (authorizationHeader != null) {
processBasicAuthorization(authorizationHeader, ctx);
} else if (mhqAuthorizationHeader != null) {
processMHQAuthorization(mhqAuthorizationHeader, ctx);
} else {
ctx.put("actor", new UnknownActor());
ctx.next();
processNoAuthorization(ctx);
}
}
@SuppressWarnings("deprecation") // We purposely get the User by their last username.
private void processBasicAuthorization(String authHeader, RoutingContext ctx) {
String encodedHeader = authHeader.substring("Basic ".length());
String[] credentials = new String(Base64.getDecoder().decode(encodedHeader.getBytes())).split(":");
String decodedHeader = new String(Base64.getDecoder().decode(encodedHeader.getBytes()));
String[] credentials = decodedHeader.split(":");
if (credentials.length == 2) {
User user = User.findByLastUsername(credentials[0]);
String password = credentials[1];
User.findByLastUsername(credentials[0], (user, error) -> {
if (error != null) {
String password = credentials[1];
if (user != null && user.getPassword() != null && user.checkPassword(password)) {
ctx.put("actor", new UserActor(user));
ctx.next();
return;
}
if (user != null && user.getPassword() != null && user.checkPassword(password)) {
ctx.put("actor", new UserActor(user));
ctx.next();
return;
}
}
ctx.response().putHeader("WWW-Authenticate", "Basic realm=\"MineHQ\"");
ErrorUtils.respondGeneric(ctx, "Failed to authorize as " + credentials[0] + ".");
});
} else {
ctx.response().putHeader("WWW-Authenticate", "Basic realm=\"MineHQ\"");
ErrorUtils.respondGeneric(ctx, "Failed to authorize as " + credentials[0] + ".");
}
ctx.response().putHeader("WWW-Authenticate", "Basic realm=\"MineHQ\"");
ErrorUtils.respondGeneric(ctx, "Failed to authorize as " + credentials[0] + ".");
}
private void processMHQAuthorization(String authHeader, RoutingContext ctx) {
@ -93,4 +98,9 @@ public final class ActorAttributeFilter implements Handler<RoutingContext> {
ErrorUtils.respondGeneric(ctx, "Failed to authorize.");
}
public void processNoAuthorization(RoutingContext ctx) {
ctx.put("actor", new UnknownActor());
ctx.next();
}
}

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.filters;
package net.frozenorb.apiv3.handlers;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
@ -6,8 +6,9 @@ import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.utils.ErrorUtils;
public final class AuthorizationFilter implements Handler<RoutingContext> {
public final class AuthorizationHandler implements Handler<RoutingContext> {
@Override
public void handle(RoutingContext ctx) {
Actor actor = ctx.get("actor");

View File

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

View File

@ -1,9 +1,9 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableMap;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
@ -15,12 +15,8 @@ import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.*;
@Entity
public final class AuditLogEntry {
private static final MongoCollection<AuditLogEntry> auditLogCollection = APIv3.getDatabase().getCollection("auditLog", AuditLogEntry.class);
@ -34,22 +30,46 @@ public final class AuditLogEntry {
@Getter private AuditLogActionType type;
@Getter private Map<String, Object> metadata;
public static List<AuditLogEntry> findAll() {
public static List<AuditLogEntry> findAllSync() {
return SyncUtils.blockMulti(auditLogCollection.find());
}
public static AuditLogEntry findById(String id) {
public static List<AuditLogEntry> findAllPaginatedSync(int skip, int pageSize) {
return SyncUtils.blockMulti(auditLogCollection.find().sort(new Document("performedAt", 1)).skip(skip).limit(pageSize));
}
public static AuditLogEntry findByIdSync(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> findByUserSync(User user) {
return findByUserSync(user.getId());
}
public static List<AuditLogEntry> findByUser(UUID user) {
public static List<AuditLogEntry> findByUserSync(UUID user) {
return SyncUtils.blockMulti(auditLogCollection.find(new Document("user", user)));
}
public static void findAll(SingleResultCallback<List<AuditLogEntry>> callback) {
auditLogCollection.find().into(new ArrayList<>(), callback);
}
public static void findAllPaginated(int skip, int pageSize, SingleResultCallback<List<AuditLogEntry>> callback) {
auditLogCollection.find().sort(new Document("performedAt", 1)).skip(skip).limit(pageSize).into(new ArrayList<>(), callback);
}
public static void findById(String id, SingleResultCallback<AuditLogEntry> callback) {
auditLogCollection.find(new Document("_id", id)).first(callback);
}
public static void findByUser(User user, SingleResultCallback<List<AuditLogEntry>> callback) {
findByUser(user.getId(), callback);
}
public static void findByUser(UUID user, SingleResultCallback<List<AuditLogEntry>> callback) {
auditLogCollection.find(new Document("user", user)).into(new ArrayList<>(), callback);
}
public AuditLogEntry() {} // For Morphia
public AuditLogEntry(User user, String userIp, Actor actor, AuditLogActionType type, Map<String, Object> metadata) {

View File

@ -1,11 +1,10 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.Collections2;
import com.mongodb.async.SingleResultCallback;
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 fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
@ -16,7 +15,6 @@ import org.bson.types.ObjectId;
import java.util.*;
@Entity
@AllArgsConstructor
public final class Grant {
@ -36,26 +34,74 @@ public final class Grant {
@Getter private Date removedAt;
@Getter private String removalReason;
public static List<Grant> findAll() {
public static List<Grant> findAllSync() {
return SyncUtils.blockMulti(grantsCollection.find());
}
public static List<Grant> findByRank(Iterable<Rank> ranks) {
public static List<Grant> findAllPaginatedSync(int skip, int pageSize) {
return SyncUtils.blockMulti(grantsCollection.find().sort(new Document("addedAt", 1)).skip(skip).limit(pageSize));
}
public static List<Grant> findByRankSync(Iterable<Rank> ranks) {
return SyncUtils.blockMulti(grantsCollection.find(new Document("rank", new Document("$in", ranks))));
}
public static Grant findById(String id) {
public static Grant findByIdSync(String id) {
return SyncUtils.blockOne(grantsCollection.find(new Document("_id", id)));
}
public static List<Grant> findByUser(User user) {
return findByUser(user.getId());
public static List<Grant> findByUserSync(User user) {
return findByUserSync(user.getId());
}
public static List<Grant> findByUser(UUID user) {
public static List<Grant> findByUserSync(UUID user) {
return SyncUtils.blockMulti(grantsCollection.find(new Document("user", user)));
}
public static void findAll(SingleResultCallback<List<Grant>> callback) {
grantsCollection.find().into(new ArrayList<>(), callback);
}
public static void findAllPaginated(int skip, int pageSize, SingleResultCallback<List<Grant>> callback) {
grantsCollection.find().sort(new Document("addedAt", 1)).skip(skip).limit(pageSize).into(new ArrayList<>(), callback);
}
public static void findByRank(Iterable<Rank> ranks, SingleResultCallback<List<Grant>> callback) {
grantsCollection.find(new Document("rank", new Document("$in", ranks))).into(new ArrayList<>(), callback);
}
public static void findById(String id, SingleResultCallback<Grant> callback) {
grantsCollection.find(new Document("_id", id)).first(callback);
}
public static void findByUser(User user, SingleResultCallback<List<Grant>> callback) {
findByUser(user.getId(), callback);
}
public static void findByUser(UUID user, SingleResultCallback<List<Grant>> callback) {
grantsCollection.find(new Document("user", user)).into(new ArrayList<>(), callback);
}
public static void findByUserGrouped(Iterable<UUID> users, SingleResultCallback<Map<UUID, List<Grant>>> callback) {
grantsCollection.find(new Document("user", new Document("$in", users))).into(new ArrayList<>(), (grants, error) -> {
if (error != null) {
callback.onResult(null, error);
} else {
Map<UUID, List<Grant>> result = new HashMap<>();
for (UUID user : users) {
result.put(user, new ArrayList<>());
}
for (Grant grant : grants) {
result.get(grant.getUser()).add(grant);
}
callback.onResult(result, null);
}
});
}
public Grant() {} // For Morphia
public Grant(User user, String reason, Set<ServerGroup> scopes, Rank rank, Date expiresAt, User addedBy) {

View File

@ -1,8 +1,9 @@
package net.frozenorb.apiv3.models;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection;
import eu.dozd.mongo.annotation.Entity;
import eu.dozd.mongo.annotation.Id;
import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
@ -11,11 +12,11 @@ import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.UUID;
@Entity
@AllArgsConstructor
public final class IPLogEntry {
@ -28,30 +29,54 @@ public final class IPLogEntry {
@Getter private Date lastSeenAt;
@Getter private int uses;
public static List<IPLogEntry> findAll() {
public static List<IPLogEntry> findAllSync() {
return SyncUtils.blockMulti(ipLogCollection.find());
}
public static IPLogEntry findById(String id) {
public static IPLogEntry findByIdSync(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> findByUserSync(User user) {
return findByUserSync(user.getId());
}
public static List<IPLogEntry> findByUser(UUID user) {
public static List<IPLogEntry> findByUserSync(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 findByUserAndIpSync(User user, String userIp) {
return findByUserAndIpSync(user.getId(), userIp);
}
public static IPLogEntry findByUserAndIp(UUID user, String userIp) {
public static IPLogEntry findByUserAndIpSync(UUID user, String userIp) {
return SyncUtils.blockOne(ipLogCollection.find(new Document("user", user).append("userIp", userIp)));
}
public static void findAll(SingleResultCallback<List<IPLogEntry>> callback) {
ipLogCollection.find().into(new ArrayList<>(), callback);
}
public static void findById(String id, SingleResultCallback<IPLogEntry> callback) {
ipLogCollection.find(new Document("_id", id)).first(callback);
}
public static void findByUser(User user, SingleResultCallback<List<IPLogEntry>> callback) {
findByUser(user.getId(), callback);
}
public static void findByUser(UUID user, SingleResultCallback<List<IPLogEntry>> callback) {
ipLogCollection.find(new Document("user", user)).into(new ArrayList<>(), callback);
}
public static void findByUserAndIp(User user, String userIp, SingleResultCallback<IPLogEntry> callback) {
findByUserAndIp(user.getId(), userIp, callback);
}
public static void findByUserAndIp(UUID user, String userIp, SingleResultCallback<IPLogEntry> callback) {
ipLogCollection.find(new Document("user", user).append("userIp", userIp)).first(callback);
}
public IPLogEntry() {} // For Morphia
public IPLogEntry(User user, String userIp) {
@ -74,4 +99,10 @@ public final class IPLogEntry {
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
ipLogCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
}

View File

@ -1,21 +1,22 @@
package net.frozenorb.apiv3.models;
import com.mongodb.async.SingleResultCallback;
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 fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.unsorted.Notification;
import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Entity
public final class NotificationTemplate {
private static final MongoCollection<NotificationTemplate> notificationTemplatesCollection = APIv3.getDatabase().getCollection("notificationTemplates", NotificationTemplate.class);
@ -24,14 +25,22 @@ public final class NotificationTemplate {
@Getter @Setter private String subject;
@Getter @Setter private String body;
public static List<NotificationTemplate> findAll() {
public static List<NotificationTemplate> findAllSync() {
return SyncUtils.blockMulti(notificationTemplatesCollection.find());
}
public static NotificationTemplate findById(String id) {
public static NotificationTemplate findByIdSync(String id) {
return SyncUtils.blockOne(notificationTemplatesCollection.find(new Document("_id", id)));
}
public static void findAll(SingleResultCallback<List<NotificationTemplate>> callback) {
notificationTemplatesCollection.find().into(new ArrayList<>(), callback);
}
public static void findById(String id, SingleResultCallback<NotificationTemplate> callback) {
notificationTemplatesCollection.find(new Document("_id", id)).first(callback);
}
public NotificationTemplate() {} // For Morphia
public NotificationTemplate(String id, String subject, String body) {

View File

@ -1,9 +1,9 @@
package net.frozenorb.apiv3.models;
import com.mongodb.async.SingleResultCallback;
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 fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
@ -17,7 +17,6 @@ import org.bson.types.ObjectId;
import java.util.*;
@Entity
@AllArgsConstructor
public final class Punishment {
@ -39,34 +38,90 @@ public final class Punishment {
@Getter private Date removedAt;
@Getter private String removalReason;
public static List<Punishment> findAll() {
public static List<Punishment> findAllSync() {
return SyncUtils.blockMulti(punishmentsCollection.find());
}
public static List<Punishment> findByType(Iterable<PunishmentType> types) {
public static List<Punishment> findAllPaginatedSync(int skip, int pageSize) {
return SyncUtils.blockMulti(punishmentsCollection.find().sort(new Document("addedAt", 1)).skip(skip).limit(pageSize));
}
public static List<Punishment> findByTypeSync(Iterable<PunishmentType> types) {
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("type", new Document("$in", types))));
}
public static Punishment findById(String id) {
public static Punishment findByIdSync(String id) {
return SyncUtils.blockOne(punishmentsCollection.find(new Document("_id", id)));
}
public static List<Punishment> findByUser(User user) {
return findByUser(user.getId());
public static List<Punishment> findByUserSync(User user) {
return findByUserSync(user.getId());
}
public static List<Punishment> findByUser(UUID user) {
public static List<Punishment> findByUserSync(UUID user) {
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("user", user)));
}
public static List<Punishment> findByUserAndType(User user, Iterable<PunishmentType> types) {
return findByUserAndType(user.getId(), types);
public static List<Punishment> findByUserAndTypeSync(User user, Iterable<PunishmentType> types) {
return findByUserAndTypeSync(user.getId(), types);
}
public static List<Punishment> findByUserAndType(UUID user, Iterable<PunishmentType> types) {
public static List<Punishment> findByUserAndTypeSync(UUID user, Iterable<PunishmentType> types) {
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("user", user).append("type", new Document("$in", types))));
}
public static void findAll(SingleResultCallback<List<Punishment>> callback) {
punishmentsCollection.find().into(new ArrayList<>(), callback);
}
public static void findAllPaginated(int skip, int pageSize, SingleResultCallback<List<Punishment>> callback) {
punishmentsCollection.find().sort(new Document("addedAt", 1)).skip(skip).limit(pageSize).into(new ArrayList<>(), callback);
}
public static void findByType(Iterable<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) {
punishmentsCollection.find(new Document("type", new Document("$in", types))).into(new ArrayList<>(), callback);
}
public static void findById(String id, SingleResultCallback<Punishment> callback) {
punishmentsCollection.find(new Document("_id", id)).first(callback);
}
public static void findByUser(User user, SingleResultCallback<List<Punishment>> callback) {
findByUser(user.getId(), callback);
}
public static void findByUser(UUID user, SingleResultCallback<List<Punishment>> callback) {
punishmentsCollection.find(new Document("user", user)).into(new ArrayList<>(), callback);
}
public static void findByUserGrouped(Iterable<UUID> users, SingleResultCallback<Map<UUID, List<Punishment>>> callback) {
punishmentsCollection.find(new Document("user", new Document("$in", users))).into(new ArrayList<>(), (punishments, error) -> {
if (error != null) {
callback.onResult(null, error);
} else {
Map<UUID, List<Punishment>> result = new HashMap<>();
for (UUID user : users) {
result.put(user, new ArrayList<>());
}
for (Punishment punishment : punishments) {
result.get(punishment.getUser()).add(punishment);
}
callback.onResult(result, null);
}
});
}
public static void findByUserAndType(User user, Iterable<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) {
findByUserAndType(user.getId(), types, callback);
}
public static void findByUserAndType(UUID user, Iterable<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) {
punishmentsCollection.find(new Document("user", user).append("type", new Document("$in", types))).into(new ArrayList<>(), callback);
}
public Punishment() {} // For Morphia
public Punishment(User user, String reason, PunishmentType type, Date expiresAt, User addedBy, Actor actor, Map<String, Object> metadata) {

View File

@ -5,8 +5,7 @@ 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 fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
@ -19,7 +18,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Entity
public final class Rank {
private static final MongoCollection<Rank> ranksCollection = APIv3.getDatabase().getCollection("ranks", Rank.class);

View File

@ -4,8 +4,7 @@ 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 fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
@ -17,7 +16,6 @@ import org.bson.Document;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Entity
public final class Server {
private static final MongoCollection<Server> serversCollection = APIv3.getDatabase().getCollection("servers", Server.class);

View File

@ -4,8 +4,7 @@ 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 fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
@ -18,7 +17,6 @@ import org.bson.Document;
import java.util.*;
import java.util.concurrent.TimeUnit;
@Entity
public final class ServerGroup {
private static final MongoCollection<ServerGroup> serverGroupsCollection = APIv3.getDatabase().getCollection("serverGroups", ServerGroup.class);
@ -29,8 +27,6 @@ public final class ServerGroup {
@Getter @Id private String id;
@Getter private String image;
// 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<>();
@ -48,10 +44,9 @@ public final class ServerGroup {
public ServerGroup() {} // For Morphia
public ServerGroup(String id, String image, boolean isPublic) {
public ServerGroup(String id, String image) {
this.id = id;
this.image = image;
this.isPublic = isPublic;
}
public Map<String, Boolean> calculatePermissions(Rank userRank) {

View File

@ -1,18 +1,22 @@
package net.frozenorb.apiv3.models;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
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.SingleResultCallback;
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 fr.javatic.mongo.jacksonCodec.objectId.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.serialization.UUIDJsonDeserializer;
import net.frozenorb.apiv3.serialization.UUIDJsonSerializer;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.MojangUtils;
import net.frozenorb.apiv3.utils.PermissionUtils;
@ -22,13 +26,12 @@ import org.bson.Document;
import java.util.*;
@Entity
@AllArgsConstructor
public final class User {
private static final MongoCollection<User> usersCollection = APIv3.getDatabase().getCollection("users", User.class);
@Getter @Id private UUID id;
@Getter @Id @JsonSerialize(using=UUIDJsonSerializer.class) @JsonDeserialize(using=UUIDJsonDeserializer.class) private UUID id;
@Getter private String lastUsername;
@Getter @ExcludeFromReplies private Map<String, Date> aliases = new HashMap<>();
@Getter @ExcludeFromReplies @Setter private String totpSecret;
@ -42,11 +45,11 @@ public final class User {
@Getter private Date firstSeenAt;
@Getter private boolean online;
public static List<User> findAll() {
public static List<User> findAllSync() {
return SyncUtils.blockMulti(usersCollection.find());
}
public static User findById(String id) {
public static User findByIdSync(String id) {
UUID uuid;
try {
@ -55,10 +58,10 @@ public final class User {
return null;
}
return findById(uuid);
return findByIdSync(uuid);
}
public static User findById(UUID id) {
public static User findByIdSync(UUID id) {
if (UUIDUtils.isAcceptableUUID(id)) {
return SyncUtils.blockOne(usersCollection.find(new Document("_id", id)));
} else {
@ -66,14 +69,63 @@ public final class User {
}
}
public static User findByEmailToken(String emailToken) {
public static User findByEmailTokenSync(String emailToken) {
return SyncUtils.blockOne(usersCollection.find(new Document("emailToken", emailToken)));
}
public static User findByLastUsername(String lastUsername) {
public static User findByLastUsernameSync(String lastUsername) {
return SyncUtils.blockOne(usersCollection.find(new Document("lastUsername", lastUsername)));
}
public static void findAll(SingleResultCallback<List<User>> callback) {
usersCollection.find().into(new ArrayList<>(), callback);
}
public static void findById(String id, SingleResultCallback<User> callback) {
try {
UUID uuid = UUID.fromString(id);
findById(uuid, callback);
} catch (IllegalArgumentException ex) { // from UUID parsing
callback.onResult(null, ex);
}
}
public static void findById(UUID id, SingleResultCallback<User> callback) {
if (UUIDUtils.isAcceptableUUID(id)) {
usersCollection.find(new Document("_id", id)).first(callback);
} else {
callback.onResult(null, null);
}
}
public static void findByIdGrouped(Iterable<UUID> search, SingleResultCallback<Map<UUID, User>> callback) {
usersCollection.find(new Document("_id", new Document("$in", search))).into(new ArrayList<>(), (users, error) -> {
if (error != null) {
callback.onResult(null, error);
} else {
Map<UUID, User> result = new HashMap<>();
for (UUID user : search) {
result.put(user, null);
}
for (User user : users) {
result.put(user.getId(), user);
}
callback.onResult(result, null);
}
});
}
public static void findByEmailToken(String emailToken, SingleResultCallback<User> callback) {
usersCollection.find(new Document("emailToken", emailToken)).first(callback);
}
public static void findByLastUsername(String lastUsername, SingleResultCallback<User> callback) {
usersCollection.find(new Document("lastUsername", lastUsername)).first(callback);
}
public User() {} // For Morphia
public User(UUID id, String lastUsername) {
@ -134,8 +186,10 @@ public final class User {
User withNewUsername;
while ((withNewUsername = User.findByLastUsername(username)) != null) {
String newUsername = MojangUtils.getName(withNewUsername.getId());
while ((withNewUsername = User.findByLastUsernameSync(username)) != null) {
BlockingCallback<String> callback = new BlockingCallback<>();
MojangUtils.getName(withNewUsername.getId(), callback);
String newUsername = callback.get();
withNewUsername.updateUsername(newUsername);
}
}
@ -164,7 +218,7 @@ public final class User {
}
public Rank getHighestRankScoped(ServerGroup serverGroup) {
return getHighestRankScoped(serverGroup, Grant.findByUser(this));
return getHighestRankScoped(serverGroup, Grant.findByUserSync(this));
}
// TODO: Clean
@ -195,7 +249,7 @@ public final class User {
public Map<ServerGroup, Rank> getHighestRanks() {
Map<ServerGroup, Rank> highestRanks = new HashMap<>();
Rank defaultRank = Rank.findById("default");
List<Grant> userGrants = Grant.findByUser(this);
List<Grant> userGrants = Grant.findByUserSync(this);
for (ServerGroup serverGroup : ServerGroup.findAll()) {
Rank highest = defaultRank;
@ -221,12 +275,12 @@ public final class User {
public Map<String, Object> getLoginInfo(Server server) {
return createLoginInfo(
server,
Punishment.findByUserAndType(this, ImmutableSet.of(
Punishment.findByUserAndTypeSync(this, ImmutableSet.of(
Punishment.PunishmentType.BLACKLIST,
Punishment.PunishmentType.BAN,
Punishment.PunishmentType.MUTE
)),
Grant.findByUser(this)
Grant.findByUserSync(this)
);
}

View File

@ -1,11 +1,11 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableMap;
import com.mongodb.async.SingleResultCallback;
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 fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
@ -14,11 +14,11 @@ import net.frozenorb.apiv3.utils.SyncUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.UUID;
@Entity
public final class UserMetaEntry {
private static final MongoCollection<UserMetaEntry> userMetaCollection = APIv3.getDatabase().getCollection("userMeta", UserMetaEntry.class);
@ -28,22 +28,38 @@ public final class UserMetaEntry {
@Getter private String serverGroup;
@Getter @Setter private Map<String, Object> data;
public static List<UserMetaEntry> findAll() {
public static List<UserMetaEntry> findAllSync() {
return SyncUtils.blockMulti(userMetaCollection.find());
}
public static UserMetaEntry findById(String id) {
public static UserMetaEntry findByIdSync(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 findByUserAndGroupSync(User user, ServerGroup serverGroup) {
return findByUserAndGroupSync(user.getId(), serverGroup);
}
public static UserMetaEntry findByUserAndGroup(UUID user, ServerGroup serverGroup) {
public static UserMetaEntry findByUserAndGroupSync(UUID user, ServerGroup serverGroup) {
return SyncUtils.blockOne(userMetaCollection.find(new Document("user", user).append("serverGroup", serverGroup.getId())));
}
public static void findAll(SingleResultCallback<List<UserMetaEntry>> callback) {
userMetaCollection.find().into(new ArrayList<>(), callback);
}
public static void findById(String id, SingleResultCallback<UserMetaEntry> callback) {
userMetaCollection.find(new Document("_id", id)).first(callback);
}
public static void findByUserAndGroup(User user, ServerGroup serverGroup, SingleResultCallback<UserMetaEntry> callback) {
findByUserAndGroup(user.getId(), serverGroup, callback);
}
public static void findByUserAndGroup(UUID user, ServerGroup serverGroup, SingleResultCallback<UserMetaEntry> callback) {
userMetaCollection.find(new Document("user", user).append("serverGroup", serverGroup.getId())).first(callback);
}
public UserMetaEntry() {} // For Morphia
public UserMetaEntry(User user, ServerGroup serverGroup, Map<String, Object> data) {

View File

@ -29,8 +29,7 @@ public final class GETDump implements Handler<RoutingContext> {
List<UUID> banCache = new ArrayList<>();
List<UUID> blacklistCache = new ArrayList<>();
Punishment.findByType
(ImmutableSet.of(
Punishment.findByTypeSync(ImmutableSet.of(
Punishment.PunishmentType.BAN,
Punishment.PunishmentType.BLACKLIST
)).forEach((punishment) -> {
@ -52,7 +51,7 @@ public final class GETDump implements Handler<RoutingContext> {
if (tick == 0 || tick % 2 == 0) {
Map<String, List<UUID>> grantCache = new HashMap<>();
Grant.findAll().forEach((grant) -> {
Grant.findAllSync().forEach((grant) -> {
if (grant.isActive()) {
List<UUID> users = grantCache.get(grant.getRank());

View File

@ -10,12 +10,12 @@ public final class GETAuditLog implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
try {
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"));
int skip = ctx.request().getParam("skip") == null ? 0 : Integer.parseInt(ctx.request().getParam("skip"));
int pageSize = ctx.request().getParam("pageSize") == null ? 100 : Integer.parseInt(ctx.request().getParam("pageSize"));
APIv3.respondJson(ctx, APIv3.getDatastore().createQuery(AuditLogEntry.class).order("performedAt").limit(limit).offset(offset).asList());
APIv3.respondJson(ctx, AuditLogEntry.findAllPaginatedSync(skip, pageSize));
} catch (NumberFormatException ex) {
ErrorUtils.respondInvalidInput(ctx, "limit/offset must be numerical inputs.");
ErrorUtils.respondInvalidInput(ctx, "skip and pageSize must be numerical inputs.");
}
}

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.findById(ctx.request().getParam("id"));
Grant grant = Grant.findByIdSync(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.findById(ctx.request().getParam("removedBy"));
User removedBy = User.findByIdSync(ctx.request().getParam("removedBy"));
if (removedBy == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("removedBy"));

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.respondJson(ctx, Grant.findById(ctx.request().getParam("id")));
APIv3.respondJson(ctx, Grant.findByIdSync(ctx.request().getParam("id")));
}
}

View File

@ -10,12 +10,12 @@ public final class GETGrants implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
try {
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"));
int skip = ctx.request().getParam("skip") == null ? 0 : Integer.parseInt(ctx.request().getParam("skip"));
int pageSize = ctx.request().getParam("pageSize") == null ? 100 : Integer.parseInt(ctx.request().getParam("pageSize"));
APIv3.respondJson(ctx, APIv3.getDatastore().createQuery(Grant.class).order("addedAt").limit(limit).offset(offset).asList());
APIv3.respondJson(ctx, Grant.findAllPaginatedSync(skip, pageSize));
} catch (NumberFormatException ex) {
ErrorUtils.respondInvalidInput(ctx, "limit and offset must be numerical inputs.");
ErrorUtils.respondInvalidInput(ctx, "skip and pageSize must be numerical inputs.");
}
}

View File

@ -10,14 +10,14 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserGrants implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findById(ctx.request().getParam("id"));
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
APIv3.respondJson(ctx, Grant.findByUser(target));
APIv3.respondJson(ctx, Grant.findByUserSync(target));
}
}

View File

@ -16,7 +16,7 @@ import java.util.Set;
public final class POSTUserGrant implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findById(ctx.request().getParam("id"));
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -67,7 +67,7 @@ public final class POSTUserGrant implements Handler<RoutingContext> {
}
// We purposely don't do a null check, grants don't have to have a source.
User addedBy = User.findById(ctx.request().getParam("addedBy"));
User addedBy = User.findByIdSync(ctx.request().getParam("addedBy"));
Grant grant = new Grant(target, reason, scopes, rank, expiresAt, addedBy);
grant.insert();

View File

@ -10,14 +10,14 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserIPLog implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findById(ctx.request().getParam("id"));
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
APIv3.respondJson(ctx, IPLogEntry.findByUser(target));
APIv3.respondJson(ctx, IPLogEntry.findByUserSync(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.findById(ctx.request().getParam("id"));
NotificationTemplate notificationTemplate = NotificationTemplate.findByIdSync(ctx.request().getParam("id"));
if (notificationTemplate == null) {
ErrorUtils.respondNotFound(ctx, "Notification template", ctx.request().getParam("id"));

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.respondJson(ctx, NotificationTemplate.findById(ctx.request().getParam("id")));
APIv3.respondJson(ctx, NotificationTemplate.findByIdSync(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.respondJson(ctx, NotificationTemplate.findAll());
APIv3.respondJson(ctx, NotificationTemplate.findAllSync());
}
}

View File

@ -13,7 +13,7 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETEPunishment implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
Punishment punishment = Punishment.findById(ctx.request().getParam("id"));
Punishment punishment = Punishment.findByIdSync(ctx.request().getParam("id"));
if (punishment == null) {
ErrorUtils.respondNotFound(ctx, "Punishment", ctx.request().getParam("id"));
@ -23,7 +23,7 @@ public final class DELETEPunishment implements Handler<RoutingContext> {
return;
}
User removedBy = User.findById(ctx.request().getParam("removedBy"));
User removedBy = User.findByIdSync(ctx.request().getParam("removedBy"));
if (removedBy == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("removedBy"));

View File

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

View File

@ -10,12 +10,12 @@ public final class GETPunishments implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
try {
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"));
int skip = ctx.request().getParam("skip") == null ? 0 : Integer.parseInt(ctx.request().getParam("skip"));
int pageSize = ctx.request().getParam("pageSize") == null ? 100 : Integer.parseInt(ctx.request().getParam("pageSize"));
APIv3.respondJson(ctx, APIv3.getDatastore().createQuery(Punishment.class).order("addedAt").limit(limit).offset(offset).asList());
APIv3.respondJson(ctx, Punishment.findAllPaginatedSync(skip, pageSize));
} catch (NumberFormatException ex) {
ErrorUtils.respondInvalidInput(ctx, "limit and offset must be numerical inputs.");
ErrorUtils.respondInvalidInput(ctx, "skip and pageSize must be numerical inputs.");
}
}

View File

@ -10,14 +10,14 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserPunishments implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findById(ctx.request().getParam("id"));
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
APIv3.respondJson(ctx, Punishment.findByUser(target));
APIv3.respondJson(ctx, Punishment.findByUserSync(target));
}
}

View File

@ -17,7 +17,7 @@ import java.util.Map;
public final class POSTUserPunish implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findById(ctx.request().getParam("id"));
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -34,9 +34,9 @@ public final class POSTUserPunish implements Handler<RoutingContext> {
Punishment.PunishmentType type = Punishment.PunishmentType.valueOf(ctx.request().getParam("type"));
if (type != Punishment.PunishmentType.WARN) {
for (Punishment punishment : Punishment.findByUserAndType(target, ImmutableSet.of(type))) {
for (Punishment punishment : Punishment.findByUserAndTypeSync(target, ImmutableSet.of(type))) {
if (punishment.isActive()) {
ErrorUtils.respondGeneric(ctx, "A punishment by " + User.findById(punishment.getAddedBy()).getLastUsername() + " already covers this user.");
ErrorUtils.respondGeneric(ctx, "A punishment by " + User.findByIdSync(punishment.getAddedBy()).getLastUsername() + " already covers this user.");
return;
}
}
@ -63,7 +63,7 @@ public final class POSTUserPunish implements Handler<RoutingContext> {
}
// We purposely don't do a null check, grants don't have to have a source.
User addedBy = User.findById(ctx.request().getParam("addedBy"));
User addedBy = User.findByIdSync(ctx.request().getParam("addedBy"));
if (target.hasPermissionAnywhere(Permissions.PROTECTED_PUNISHMENT)) {
ErrorUtils.respondGeneric(ctx, target.getLastSeenOn() + " is protected from punishments.");

View File

@ -10,9 +10,8 @@ public final class POSTServerGroup implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
String id = ctx.request().getParam("id");
String image = ctx.request().getParam("image");
boolean isPublic = Boolean.valueOf(ctx.request().getParam("public"));
ServerGroup serverGroup = new ServerGroup(id, image, isPublic);
ServerGroup serverGroup = new ServerGroup(id, image);
serverGroup.insert();
APIv3.respondJson(ctx, serverGroup);
}

View File

@ -1,6 +1,9 @@
package net.frozenorb.apiv3.routes.servers;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import lombok.extern.slf4j.Slf4j;
@ -18,7 +21,6 @@ import java.util.*;
@Slf4j
public final class POSTServerHeartbeat implements Handler<RoutingContext> {
@SuppressWarnings("unchecked")
public void handle(RoutingContext ctx) {
Actor actor = ctx.get("actor");
@ -27,57 +29,132 @@ public final class POSTServerHeartbeat implements Handler<RoutingContext> {
return;
}
Server actorServer = Server.byId(actor.getName());
ServerGroup actorServerGroup = ServerGroup.byId(actorServer.getServerGroup());
Server actorServer = Server.findById(actor.getName());
ServerGroup actorServerGroup = ServerGroup.findById(actorServer.getServerGroup());
Document reqJson = Document.parse(ctx.getBodyAsString());
Map<UUID, String> onlinePlayersNames = new HashMap<>();
Map<UUID, User> onlinePlayersUsers;
Map<UUID, List<Grant>> onlinePlayersGrants;
Map<UUID, List<Punishment>> onlinePlayersPunishments;
Map<String, Object> playersResponse = new HashMap<>();
Map<UUID, String> playerNames = extractPlayerNames(reqJson);
// This code is messy, but we have to do db ops in parallel to avoid
// spamming Mongo with queries, so we do this.
for (Object player : (List<Object>) reqJson.get("players")) {
Document playerJson = (Document) player;
UUID uuid = UUID.fromString(playerJson.getString("uuid"));
String username = playerJson.getString("username");
if (UUIDUtils.isAcceptableUUID(uuid)) {
onlinePlayersNames.put(uuid, username);
CompositeFuture.all(
createInfoResponse(actorServer, reqJson.getDouble("tps"), playerNames),
createPlayerResponse(actorServer, playerNames),
createPermissionsResponse(actorServerGroup),
createEventsResponse((List<Object>) reqJson.get("events"))
).setHandler((result) -> {
if (result.succeeded()) {
APIv3.respondJson(ctx, ImmutableMap.of(
"info", result.result().result(0),
"players", result.result().result(1),
"permissions", result.result().result(2),
"events", result.result().result(3)
));
} else {
ErrorUtils.respondGeneric(ctx, result.cause().getMessage());
}
});
}
public Future<Map<String, Object>> createInfoResponse(Server server, double tps, Map<UUID, String> playerNames) {
Future<Map<String, Object>> callback = Future.future();
server.setPlayers(playerNames.keySet());
server.setLastTps(tps);
server.setLastUpdatedAt(new Date());
server.save();
return callback;
}
public Future<Map<String, Object>> createPlayerResponse(Server server, Map<UUID, String> playerNames) {
Future<Map<String, Object>> callback = Future.future();
Future<Map<UUID, User>> userLookupCallback = Future.future();
Future<Map<UUID, List<Grant>>> grantLookupCallback = Future.future();
Future<Map<UUID, List<Punishment>>> punishmentLookupCallback = Future.future();
User.findByIdGrouped(playerNames.keySet(), (users, error) -> {
if (error != null) {
userLookupCallback.fail(error);
} else {
userLookupCallback.complete(users);
}
});
Grant.findByUserGrouped(playerNames.keySet(), (grants, error) -> {
if (error != null) {
grantLookupCallback.fail(error);
} else {
grantLookupCallback.complete(grants);
}
});
Punishment.findByUserGrouped(playerNames.keySet(), (punishments, error) -> {
if (error != null) {
punishmentLookupCallback.fail(error);
} else {
punishmentLookupCallback.complete(punishments);
}
});
CompositeFuture.all(
userLookupCallback,
grantLookupCallback,
punishmentLookupCallback
).setHandler((result) -> {
if (result.failed()) {
callback.fail(result.cause());
} else {
Map<UUID, User> users = result.result().result(0);
Map<UUID, List<Grant>> grants = result.result().result(1);
Map<UUID, List<Punishment>> punishments = result.result().result(2);
Map<String, Object> response = new HashMap<>();
for (Map.Entry<UUID, User> userEntry : users.entrySet()) {
UUID uuid = userEntry.getKey();
User user = userEntry.getValue();
if (user == null) {
String username = playerNames.get(uuid);
user = new User(uuid, username);
user.insert();
users.put(uuid, user);
}
// Only save if needed
if (user.seenOnServer(server)) {
user.save();
}
response.put(uuid.toString(), user.createLoginInfo(server, punishments.get(uuid), grants.get(uuid)));
}
callback.complete(response);
}
});
return callback;
}
public Future<Map<String, Object>> createPermissionsResponse(ServerGroup serverGroup) {
Future<Map<String, Object>> callback = Future.future();
Map<String, Object> permissionsResponse = new HashMap<>();
for (Rank rank : Rank.findAll()) {
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
PermissionUtils.getDefaultPermissions(rank),
serverGroup.calculatePermissions(rank)
);
permissionsResponse.put(rank.getId(), scopedPermissions);
}
onlinePlayersUsers = User.byIdGrouped(onlinePlayersNames.keySet());
callback.complete(permissionsResponse);
return callback;
}
for (Map.Entry<UUID, User> entry : new HashMap<>(onlinePlayersUsers).entrySet()) {
UUID uuid = entry.getKey();
User user = entry.getValue();
public Future<Map<String, Object>> createEventsResponse(List<Object> eventsData) {
Future<Map<String, Object>> callback = Future.future();
if (user == null) {
// Will be saved in the User constructor
String username = onlinePlayersNames.get(uuid);
user = new User(uuid, username);
onlinePlayersUsers.put(uuid, user);
}
// Only save if needed
if (user.seenOnServer(actorServer)) {
APIv3.getDatastore().save(user);
}
}
onlinePlayersGrants = Grant.byUserGrouped(onlinePlayersUsers.keySet());
onlinePlayersPunishments = Punishment.byUserGrouped(onlinePlayersUsers.keySet());
for (Map.Entry<UUID, User> entry : onlinePlayersUsers.entrySet()) {
UUID uuid = entry.getKey();
User user = entry.getValue();
playersResponse.put(uuid.toString(), user.prepareLoginInfo(actorServer, onlinePlayersPunishments.get(uuid), onlinePlayersGrants.get(uuid)));
}
for (Object event : (List<Object>) reqJson.get("events")) {
for (Object event : eventsData) {
Document eventJson = (Document) event;
String type = eventJson.getString("type");
@ -93,26 +170,24 @@ public final class POSTServerHeartbeat implements Handler<RoutingContext> {
}
}
Map<String, Map<String, Boolean>> permissionsResponse = new HashMap<>();
callback.complete(ImmutableMap.of());
return callback;
}
for (Rank rank : Rank.findAll()) {
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
PermissionUtils.getDefaultPermissions(rank),
actorServerGroup.calculatePermissions(rank)
);
public Map<UUID, String> extractPlayerNames(Document reqJson) {
Map<UUID, String> result = new HashMap<>();
permissionsResponse.put(rank.getId(), scopedPermissions);
for (Object player : (List<Object>) reqJson.get("players")) {
Document playerJson = (Document) player;
UUID uuid = UUID.fromString(playerJson.getString("uuid"));
String username = playerJson.getString("username");
if (UUIDUtils.isAcceptableUUID(uuid)) {
result.put(uuid, username);
}
}
actorServer.setPlayers(onlinePlayersNames.keySet());
actorServer.setLastTps(reqJson.getDouble("lastTps"));
actorServer.setLastUpdatedAt(new Date());
actorServer.save();
APIv3.respondJson(ctx, ImmutableMap.of(
"players", playersResponse,
"permissions", permissionsResponse
));
return result;
}
}

View File

@ -12,7 +12,7 @@ import org.bson.Document;
public final class DELETEUserMeta implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -26,7 +26,7 @@ public final class DELETEUserMeta implements Handler<RoutingContext> {
return;
}
UserMetaEntry userMetaEntry = UserMetaEntry.findByUserAndGroup(user, serverGroup);
UserMetaEntry userMetaEntry = UserMetaEntry.findByUserAndGroupSync(user, serverGroup);
if (userMetaEntry != null) {
userMetaEntry.delete();

View File

@ -14,7 +14,7 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class DELETEUserPunishment implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findById(ctx.request().getParam("id"));
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -22,7 +22,7 @@ public final class DELETEUserPunishment implements Handler<RoutingContext> {
}
Punishment.PunishmentType type = Punishment.PunishmentType.valueOf(ctx.request().getParam("type").toUpperCase());
User removedBy = User.findById(ctx.request().getParam("removedBy"));
User removedBy = User.findByIdSync(ctx.request().getParam("removedBy"));
if (removedBy == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("removedBy"));
@ -36,7 +36,7 @@ public final class DELETEUserPunishment implements Handler<RoutingContext> {
return;
}
for (Punishment punishment : target.getPunishments(ImmutableSet.of(type))) {
for (Punishment punishment : Punishment.findByUserAndTypeSync(target, ImmutableSet.of(type))) {
if (punishment.isActive()) {
punishment.delete(removedBy, reason);
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of());

View File

@ -27,9 +27,9 @@ public final class GETStaff implements Handler<RoutingContext> {
return Integer.compare(firstRank.getWeight(), secondRank.getWeight());
});
Grant.findByRank(staffRanks.values()).forEach(grant -> {
Grant.findByRankSync(staffRanks.values()).forEach(grant -> {
if (grant.isActive()) {
User user = User.findById(grant.getUser());
User user = User.findByIdSync(grant.getUser());
Rank rank = staffRanks.get(grant.getRank());
if (!result.containsKey(rank.getId())) {

View File

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

View File

@ -13,7 +13,7 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserDetails implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -23,9 +23,9 @@ public final class GETUserDetails implements Handler<RoutingContext> {
// Too many fields to use .of()
APIv3.respondJson(ctx, ImmutableMap.builder()
.put("user", user)
.put("grants", Grant.findByUser(user))
.put("ipLog", IPLogEntry.findByUser(user))
.put("punishments", Punishment.findByUser(user))
.put("grants", Grant.findByUserSync(user))
.put("ipLog", IPLogEntry.findByUserSync(user))
.put("punishments", Punishment.findByUserSync(user))
.put("aliases", user.getAliases())
.put("totpSetup", user.getTotpSecret() != null)
.build()

View File

@ -11,7 +11,7 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserMeta implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -25,7 +25,7 @@ public final class GETUserMeta implements Handler<RoutingContext> {
return;
}
UserMetaEntry userMetaEntry = UserMetaEntry.findByUserAndGroup(user, serverGroup);
UserMetaEntry userMetaEntry = UserMetaEntry.findByUserAndGroupSync(user, serverGroup);
APIv3.respondJson(ctx, userMetaEntry.getData());
}

View File

@ -2,9 +2,11 @@ package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler;
import io.vertx.core.cli.converters.BooleanConverter;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IPUtils;
import net.frozenorb.apiv3.utils.TOTPUtils;
@ -12,7 +14,7 @@ import net.frozenorb.apiv3.utils.TOTPUtils;
public final class GETUserRequiresTOTP implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -34,7 +36,10 @@ public final class GETUserRequiresTOTP implements Handler<RoutingContext> {
return;
}
if (TOTPUtils.isPreAuthorized(user, userIp)) {
BlockingCallback<Boolean> preAuthorizedCallback = new BlockingCallback<>();
TOTPUtils.isPreAuthorized(user, userIp, preAuthorizedCallback);
if (preAuthorizedCallback.get()) {
APIv3.respondJson(ctx, ImmutableMap.of(
"required", false,
"message", "User's IP has already been validated"

View File

@ -10,7 +10,7 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
public final class GETUserVerifyPassword implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));

View File

@ -22,7 +22,7 @@ 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.findByEmailToken(ctx.request().getParam("emailToken"));
User user = User.findByEmailTokenSync(ctx.request().getParam("emailToken"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "Email token", ctx.request().getParam("emailToken"));

View File

@ -10,7 +10,7 @@ public class POSTUserLeave implements Handler<RoutingContext> {
@Override
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));

View File

@ -5,6 +5,7 @@ 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.IPLogEntry;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
@ -23,7 +24,7 @@ public final class POSTUserLogin implements Handler<RoutingContext> {
return;
}
User user = User.findById(uuid);
User user = User.findByIdSync(uuid);
String username = ctx.request().getParam("username");
String userIp = ctx.request().getParam("userIp");
Actor actor = ctx.get("actor");
@ -45,7 +46,19 @@ public final class POSTUserLogin implements Handler<RoutingContext> {
Server actorServer = Server.findById(actor.getName());
user.getIPLogEntry(userIp).used();
IPLogEntry ipLogEntry = IPLogEntry.findByUserAndIpSync(user, userIp);
// We use a little bit more verbose code here to save on the
// overhead of a .insert() immediately followed by a .save()
if (ipLogEntry == null) {
ipLogEntry = new IPLogEntry(user, userIp);
ipLogEntry.used();
ipLogEntry.insert();
} else {
ipLogEntry.used();
ipLogEntry.save();
}
user.updateUsername(username);
APIv3.respondJson(ctx, user.getLoginInfo(actorServer));
}

View File

@ -6,6 +6,7 @@ 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.BlockingCallback;
import net.frozenorb.apiv3.unsorted.Notification;
import net.frozenorb.apiv3.utils.ErrorUtils;
@ -15,7 +16,7 @@ import java.util.Map;
public final class POSTUserNotify implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -27,7 +28,7 @@ public final class POSTUserNotify implements Handler<RoutingContext> {
return;
}
NotificationTemplate template = NotificationTemplate.findById(ctx.request().getParam("template"));
NotificationTemplate template = NotificationTemplate.findByIdSync(ctx.request().getParam("template"));
if (template == null) {
ErrorUtils.respondNotFound(ctx, "Notification template", ctx.request().getParam("template"));
@ -49,7 +50,10 @@ public final class POSTUserNotify implements Handler<RoutingContext> {
try {
Notification notification = new Notification(template, subjectReplacements, bodyReplacements);
notification.sendAsEmail(user.getEmail());
BlockingCallback<Void> callback = new BlockingCallback<>();
notification.sendAsEmail(user.getEmail(), callback);
callback.get();
APIv3.respondJson(ctx, ImmutableMap.of(
"success", true
));

View File

@ -6,6 +6,7 @@ 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.BlockingCallback;
import net.frozenorb.apiv3.unsorted.Notification;
import net.frozenorb.apiv3.utils.ErrorUtils;
@ -24,7 +25,7 @@ public final class POSTUserRegister implements Handler<RoutingContext> {
);
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -59,10 +60,12 @@ public final class POSTUserRegister implements Handler<RoutingContext> {
"emailToken", user.getEmailToken()
);
Notification notification = new Notification(NotificationTemplate.findById("email-confirmation"), replacements, replacements);
Notification notification = new Notification(NotificationTemplate.findByIdSync("email-confirmation"), replacements, replacements);
try {
notification.sendAsEmail(user.getEmail());
BlockingCallback<Void> callback = new BlockingCallback<>();
notification.sendAsEmail(user.getEmail(), callback);
callback.get();
APIv3.respondJson(ctx, ImmutableMap.of(
"success", true
));

View File

@ -12,7 +12,7 @@ import net.frozenorb.apiv3.utils.TOTPUtils;
public final class POSTUserSetupTOTP implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));

View File

@ -2,9 +2,12 @@ package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler;
import io.vertx.core.cli.converters.BooleanConverter;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IPUtils;
import net.frozenorb.apiv3.utils.TOTPUtils;
@ -14,7 +17,7 @@ import java.util.concurrent.TimeUnit;
public final class POSTUserVerifyTOTP implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -34,8 +37,10 @@ public final class POSTUserVerifyTOTP implements Handler<RoutingContext> {
}
int providedCode = Integer.parseInt(ctx.request().getParam("code"));
BlockingCallback<Boolean> recentlyUsedCallback = new BlockingCallback<>();
TOTPUtils.wasRecentlyUsed(user, providedCode, recentlyUsedCallback);
if (TOTPUtils.wasRecentlyUsed(user, providedCode)) {
if (recentlyUsedCallback.get()) {
APIv3.respondJson(ctx, ImmutableMap.of(
"authorized", false,
"message", "TOTP code was recently used."
@ -46,8 +51,13 @@ public final class POSTUserVerifyTOTP implements Handler<RoutingContext> {
boolean authorized = TOTPUtils.authorizeUser(user, providedCode);
if (authorized) {
TOTPUtils.markPreAuthorized(user, userIp, 3, TimeUnit.DAYS);
TOTPUtils.markRecentlyUsed(user, providedCode);
BlockingCallback<Void> markPreAuthorizedCallback = new BlockingCallback<>();
TOTPUtils.markPreAuthorized(user, userIp, 3, TimeUnit.DAYS, markPreAuthorizedCallback);
markPreAuthorizedCallback.get();
BlockingCallback<Void> markRecentlyUsedCallback = new BlockingCallback<>();
TOTPUtils.markRecentlyUsed(user, providedCode, markRecentlyUsedCallback);
markRecentlyUsedCallback.get();
APIv3.respondJson(ctx, ImmutableMap.of(
"authorized", true,

View File

@ -5,13 +5,14 @@ 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 PUTUserMeta implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findById(ctx.request().getParam("id"));
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
@ -26,8 +27,16 @@ public final class PUTUserMeta implements Handler<RoutingContext> {
}
Document data = Document.parse(ctx.getBodyAsString());
UserMetaEntry metaEntry = UserMetaEntry.findByUserAndGroupSync(user, serverGroup);
if (metaEntry == null) {
metaEntry = new UserMetaEntry(user, serverGroup, data);
metaEntry.insert();
} else {
metaEntry.setData(data);
metaEntry.save();
}
user.saveMeta(serverGroup, data);
APIv3.respondJson(ctx, data);
}

View File

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

View File

@ -1,23 +0,0 @@
package net.frozenorb.apiv3.serialization;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import org.bson.types.ObjectId;
import java.io.IOException;
public final class ObjectIdTypeAdapter extends TypeAdapter<ObjectId> {
public void write(JsonWriter writer, ObjectId write) throws IOException {
writer.value(write.toString());
}
// This is used with Gson, which is only used
// to serialize outgoing responses, thus we
// don't need to have a read method.
public ObjectId read(JsonReader reader) {
throw new IllegalArgumentException();
}
}

View File

@ -0,0 +1,18 @@
package net.frozenorb.apiv3.serialization;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import java.io.IOException;
import java.util.UUID;
public class UUIDJsonDeserializer extends JsonDeserializer<UUID> {
@Override
public UUID deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException, JsonProcessingException {
return UUID.fromString(jsonParser.getValueAsString());
}
}

View File

@ -0,0 +1,18 @@
package net.frozenorb.apiv3.serialization;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import java.io.IOException;
import java.util.UUID;
public final class UUIDJsonSerializer extends JsonSerializer<UUID> {
@Override
public void serialize(UUID uuid, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException, JsonProcessingException {
jsonGenerator.writeString(uuid.toString());
}
}

View File

@ -9,6 +9,7 @@ public final class BlockingCallback<T> implements SingleResultCallback<T> {
private final SettableFuture<T> future = SettableFuture.create();
@Override
public void onResult(T val, Throwable error) {
if (error != null) {
future.setException(error);

View File

@ -5,6 +5,7 @@ import com.cribbstechnologies.clients.mandrill.model.MandrillHtmlMessage;
import com.cribbstechnologies.clients.mandrill.model.MandrillMessageRequest;
import com.cribbstechnologies.clients.mandrill.model.MandrillRecipient;
import com.cribbstechnologies.clients.mandrill.request.MandrillMessagesRequest;
import com.mongodb.async.SingleResultCallback;
import com.twilio.sdk.TwilioRestException;
import com.twilio.sdk.resource.factory.MessageFactory;
import net.frozenorb.apiv3.APIv3;
@ -32,7 +33,7 @@ public final class Notification {
this.body = template.fillBody(bodyReplacements);
}
public void sendAsEmail(String email) throws IOException {
public void sendAsEmail(String email, SingleResultCallback<Void> callback) throws IOException {
MandrillHtmlMessage message = new MandrillHtmlMessage();
message.setFrom_email("no-reply@minehq.com");
@ -43,31 +44,49 @@ public final class Notification {
new MandrillRecipient(null, email)
});
try {
MandrillMessageRequest request = new MandrillMessageRequest();
request.setMessage(message);
mandrillMessagesRequest.sendMessage(request);
APIv3.getStatsD().incrementCounter("apiv3.notification.email.success");
} catch (RequestFailedException ex) {
APIv3.getStatsD().incrementCounter("apiv3.notification.email.failure");
throw new IOException("Failed to send notification to user", ex);
}
APIv3.getVertxInstance().executeBlocking((future) -> {
try {
MandrillMessageRequest request = new MandrillMessageRequest();
request.setMessage(message);
mandrillMessagesRequest.sendMessage(request);
APIv3.getStatsD().incrementCounter("apiv3.notification.email.success");
future.succeeded();
} catch (RequestFailedException ex) {
APIv3.getStatsD().incrementCounter("apiv3.notification.email.failure");
future.fail(new IOException("Failed to send notification to user", ex));
}
}, (result) -> {
if (result.succeeded()) {
callback.onResult(null, null);
} else {
callback.onResult(null, result.cause());
}
});
}
public void sendAsText(String phoneNumber) throws IOException {
public void sendAsText(String phoneNumber, SingleResultCallback<Void> callback) throws IOException {
List<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("To", phoneNumber));
params.add(new BasicNameValuePair("From", "+13108795180"));
params.add(new BasicNameValuePair("Body", body));
try {
twillioMessageFactory.create(params);
APIv3.getStatsD().incrementCounter("apiv3.notification.text.success");
} catch (TwilioRestException ex) {
APIv3.getStatsD().incrementCounter("apiv3.notification.text.failure");
throw new IOException("Failed to send notification to user", ex);
}
APIv3.getVertxInstance().executeBlocking((future) -> {
try {
twillioMessageFactory.create(params);
APIv3.getStatsD().incrementCounter("apiv3.notification.text.success");
future.succeeded();
} catch (TwilioRestException ex) {
APIv3.getStatsD().incrementCounter("apiv3.notification.text.failure");
future.fail(new IOException("Failed to send notification to user", ex));
}
}, (result) -> {
if (result.succeeded()) {
callback.onResult(null, null);
} else {
callback.onResult(null, result.cause());
}
});
}
}

View File

@ -6,5 +6,6 @@ import lombok.experimental.UtilityClass;
public class Permissions {
public static final String PROTECTED_PUNISHMENT = "minehq.punishment.protected";
public static final String SIGN_API_REQUEST = "apiv3.signRequest";
}

View File

@ -11,12 +11,6 @@ import java.util.UUID;
@UtilityClass
public class MojangUtils {
public static String getName(UUID id) {
BlockingCallback<String> callback = new BlockingCallback<>();
getName(id, callback);
return callback.get();
}
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) -> {

View File

@ -1,15 +1,18 @@
package net.frozenorb.apiv3.utils;
import com.mongodb.async.SingleResultCallback;
import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorConfig;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator;
import io.vertx.redis.RedisClient;
import io.vertx.redis.RedisOptions;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
import redis.clients.jedis.Jedis;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
@UtilityClass
public class TOTPUtils {
@ -38,34 +41,60 @@ public class TOTPUtils {
);
}
public static boolean isPreAuthorized(User user, String ip) {
try (Jedis redis = APIv3.getRedisPool().getResource()) {
return redis.exists(user.getId() + ":preAuthorizedIP:" + ip.toLowerCase());
}
public static void isPreAuthorized(User user, String ip, SingleResultCallback<Boolean> callback) {
APIv3.getRedisClient().exists(user.getId() + ":preAuthorizedIP:" + ip.toLowerCase(), (result) -> {
if (result.succeeded()) {
callback.onResult(result.result() == 1 , null);
} else {
callback.onResult(null, result.cause());
}
});
}
public static void markPreAuthorized(User user, String ip, long duration, TimeUnit unit) {
try (Jedis redis = APIv3.getRedisPool().getResource()) {
String key = user.getId() + ":preAuthorizedIP:" + ip.toLowerCase();
public static void markPreAuthorized(User user, String ip, long duration, TimeUnit unit, SingleResultCallback<Void> callback) {
String key = user.getId() + ":preAuthorizedIP:" + ip.toLowerCase();
redis.set(key, "");
redis.expire(key, (int) unit.toSeconds(duration));
}
APIv3.getRedisClient().set(key, "", (result) -> {
if (result.succeeded()) {
APIv3.getRedisClient().expire(key, (int) unit.toSeconds(duration), (result2) -> {
if (result2.succeeded()) {
callback.onResult(null, null);
} else {
callback.onResult(null, result.cause());
}
});
} else {
callback.onResult(null, result.cause());
}
});
}
public static boolean wasRecentlyUsed(User user, int code) {
try (Jedis redis = APIv3.getRedisPool().getResource()) {
return redis.exists(user.getId() + ":recentTOTPCodes:" + code);
}
public static void wasRecentlyUsed(User user, int code, SingleResultCallback<Boolean> callback) {
APIv3.getRedisClient().exists(user.getId() + ":recentTOTPCodes:" + code, (result) -> {
if (result.succeeded()) {
callback.onResult(result.result() == 1 , null);
} else {
callback.onResult(null, result.cause());
}
});
}
public static void markRecentlyUsed(User user, int code) {
try (Jedis redis = APIv3.getRedisPool().getResource()) {
String key = user.getId() + ":recentTOTPCodes:" + code;
public static void markRecentlyUsed(User user, int code, SingleResultCallback<Void> callback) {
String key = user.getId() + ":recentTOTPCodes:" + code;
redis.set(key, "");
redis.expire(key, (int) TimeUnit.MINUTES.toSeconds(5));
}
APIv3.getRedisClient().set(key, "", (result) -> {
if (result.succeeded()) {
APIv3.getRedisClient().expire(key, (int) TimeUnit.MINUTES.toSeconds(5), (result2) -> {
if (result2.succeeded()) {
callback.onResult(null, null);
} else {
callback.onResult(null, result.cause());
}
});
} else {
callback.onResult(null, result.cause());
}
});
}
}