Merge pull request #11 from FrozenOrb/experimental

Use Vertx instead of Spark
This commit is contained in:
Jonathan Halterman 2016-06-16 09:22:00 -07:00 committed by GitHub
commit 2cab2f0e7a
187 changed files with 4784 additions and 3473 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
@ -8,13 +6,8 @@ mongo.username=
mongo.password=
redis.address=localhost
redis.port=6379
http.address=0.0.0.0
http.port=80
http.workerThreads=
twillio.accountSID=AC9e2f88c5690134d29a56f698de3cd740
twillio.authToken=982592505a171d3be6b0722f5ecacc0e
mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ
bugsnag.apiKey=0e47fba8b825416b7cbc839066184509
auth.permittedUserRanks=developer,owner
auth.websiteApiKey=RVbp4hY6sCFVaf
auth.bungeeCordApiKey=6d9cf76dc9f0d23
auth.bungeeApiKey=6d9cf76dc9f0d23

115
pom.xml
View File

@ -19,6 +19,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<archive>
<manifest>
@ -33,9 +34,9 @@
<version>2.3</version>
<configuration>
<artifactSet>
<includes>
<include>*:*</include>
</includes>
<excludes>
<exclude>org.projectlombok:lombok</exclude>
</excludes>
</artifactSet>
</configuration>
<executions>
@ -55,29 +56,67 @@
<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>
<!-- Vert.x -->
<dependency>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.5</version>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-redis-client</artifactId>
<version>3.2.1</version>
</dependency>
<!-- Google Libs -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6.2</version>
</dependency>
<!-- Mongo -->
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-async</artifactId>
<version>3.0.4</version>
</dependency>
<dependency>
<groupId>fr.javatic.mongo</groupId>
<artifactId>mongo-jackson-codec</artifactId>
<version>3.2.0__0.4</version>
</dependency>
<dependency>
<groupId>de.undercouch</groupId>
<artifactId>bson4jackson</artifactId>
<version>2.6.0</version>
</dependency>
<!-- TOTP -->
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>0.5.0</version>
</dependency>
<!-- Monitoring -->
<dependency>
<groupId>com.indeed</groupId>
<artifactId>java-dogstatsd-client</artifactId>
@ -88,61 +127,15 @@
<artifactId>bugsnag</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>com.cribbstechnologies.clients</groupId>
<artifactId>mandrillClient</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>com.twilio.sdk</groupId>
<artifactId>twilio-java-sdk</artifactId>
<version>6.3.0</version>
</dependency>
<dependency>
<groupId>org.mindrot</groupId>
<artifactId>jbcrypt</artifactId>
<version>0.3m</version>
</dependency>
<dependency>
<groupId>org.mongodb.morphia</groupId>
<artifactId>morphia</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>org.mongodb.morphia</groupId>
<artifactId>morphia-logging-slf4j</artifactId>
<version>1.1.0</version>
</dependency>
<!-- Logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4</version>
</dependency>
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>1.8.0</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@ -1,154 +1,217 @@
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.common.net.MediaType;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mongodb.*;
import com.mongodb.client.MongoDatabase;
import com.timgroup.statsd.NonBlockingStatsDClient;
import com.timgroup.statsd.StatsDClient;
import com.mongodb.ConnectionString;
import com.mongodb.MongoCredential;
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.client.model.IndexModel;
import com.mongodb.connection.ClusterSettings;
import fr.javatic.mongo.jacksonCodec.JacksonCodecProvider;
import fr.javatic.mongo.jacksonCodec.ObjectMapperFactory;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpClient;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.BodyHandler;
import io.vertx.ext.web.handler.LoggerFormat;
import io.vertx.ext.web.handler.LoggerHandler;
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.*;
import net.frozenorb.apiv3.models.*;
import net.frozenorb.apiv3.routes.GETDump;
import net.frozenorb.apiv3.routes.GETWhoAmI;
import net.frozenorb.apiv3.routes.NotFound;
import net.frozenorb.apiv3.routes.POSTMetrics;
import net.frozenorb.apiv3.routes.announcements.GETAnnouncements;
import net.frozenorb.apiv3.routes.auditLog.GETAuditLog;
import net.frozenorb.apiv3.routes.chatFilterList.GETChatFilterList;
import net.frozenorb.apiv3.routes.grants.*;
import net.frozenorb.apiv3.routes.ipLog.GETUserIPLog;
import net.frozenorb.apiv3.routes.notificationTemplate.DELETENotificationTemplate;
import net.frozenorb.apiv3.routes.notificationTemplate.GETNotificationTemplate;
import net.frozenorb.apiv3.routes.notificationTemplate.GETNotificationTemplates;
import net.frozenorb.apiv3.routes.notificationTemplate.POSTNotificationTemplate;
import net.frozenorb.apiv3.routes.punishments.*;
import net.frozenorb.apiv3.routes.ranks.DELETERank;
import net.frozenorb.apiv3.routes.ranks.GETRank;
import net.frozenorb.apiv3.routes.ranks.GETRanks;
import net.frozenorb.apiv3.routes.ranks.POSTRank;
import net.frozenorb.apiv3.routes.serverGroups.DELETEServerGroup;
import net.frozenorb.apiv3.routes.serverGroups.GETServerGroup;
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.handler.ActorAttributeHandler;
import net.frozenorb.apiv3.handler.AuthorizationHandler;
import net.frozenorb.apiv3.route.GETDump;
import net.frozenorb.apiv3.route.GETWhoAmI;
import net.frozenorb.apiv3.route.POSTMetrics;
import net.frozenorb.apiv3.route.announcements.GETAnnouncements;
import net.frozenorb.apiv3.route.announcements.PUTAnnouncements;
import net.frozenorb.apiv3.route.auditLog.GETAuditLog;
import net.frozenorb.apiv3.route.auditLog.POSTUserAuditLogEntry;
import net.frozenorb.apiv3.route.chatFilterList.GETChatFilterList;
import net.frozenorb.apiv3.route.grants.*;
import net.frozenorb.apiv3.route.ipBans.*;
import net.frozenorb.apiv3.route.ipLog.GETUserIpLog;
import net.frozenorb.apiv3.route.notificationTemplates.DELETENotificationTemplate;
import net.frozenorb.apiv3.route.notificationTemplates.GETNotificationTemplate;
import net.frozenorb.apiv3.route.notificationTemplates.GETNotificationTemplates;
import net.frozenorb.apiv3.route.notificationTemplates.POSTNotificationTemplate;
import net.frozenorb.apiv3.route.punishments.*;
import net.frozenorb.apiv3.route.ranks.DELETERank;
import net.frozenorb.apiv3.route.ranks.GETRank;
import net.frozenorb.apiv3.route.ranks.GETRanks;
import net.frozenorb.apiv3.route.ranks.POSTRank;
import net.frozenorb.apiv3.route.serverGroups.DELETEServerGroup;
import net.frozenorb.apiv3.route.serverGroups.GETServerGroup;
import net.frozenorb.apiv3.route.serverGroups.GETServerGroups;
import net.frozenorb.apiv3.route.serverGroups.POSTServerGroup;
import net.frozenorb.apiv3.route.servers.*;
import net.frozenorb.apiv3.route.users.*;
import net.frozenorb.apiv3.serialization.gson.DateTypeAdapter;
import net.frozenorb.apiv3.serialization.gson.FollowAnnotationExclusionStrategy;
import net.frozenorb.apiv3.serialization.jackson.UUIDJsonDeserializer;
import net.frozenorb.apiv3.serialization.jackson.UUIDJsonSerializer;
import net.frozenorb.apiv3.serialization.mongodb.UUIDCodecProvider;
import net.frozenorb.apiv3.unsorted.BugsnagSLF4JLogger;
import net.frozenorb.apiv3.unsorted.LoggingExceptionHandler;
import net.frozenorb.apiv3.utils.IPUtils;
import net.frozenorb.apiv3.utils.PermissionUtils;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
import org.mongodb.morphia.converters.UUIDConverter;
import org.mongodb.morphia.logging.MorphiaLoggerFactory;
import org.mongodb.morphia.logging.slf4j.SLF4JLoggerImplFactory;
import redis.clients.jedis.JedisPool;
import 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.codecs.configuration.CodecRegistry;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static spark.Spark.*;
import static spark.route.RouteOverview.enableRouteOverview;
@Slf4j
public final class APIv3 {
public final class APIv3 extends AbstractVerticle {
@Getter private static Datastore datastore;
@Getter private static HttpClient httpClient;
@Getter private static MongoDatabase database;
@Getter private static Properties config = new Properties();
@Getter private static JedisPool redisPool;
@Getter private static StatsDClient statsD;
@Getter private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(ObjectId.class, new ObjectIdTypeAdapter())
@Getter private static RedisClient redisClient;
private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(Date.class, new DateTypeAdapter())
.setExclusionStrategies(new FollowAnnotationExclusionStrategy())
.create();
APIv3() {
@Override
public void start() {
setupConfig();
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", config.getProperty("logging.level"));
setupDatabase();
setupRedis();
setupMetrics();
setupBugsnag();
setupHttp();
setupHttpServer();
setupHttpClient();
//convertData("158.69.126.126", true);
LoggingFilter.setDebug(Boolean.valueOf(config.getProperty("logging.debug")));
/*V2Importer converter = new V2Importer("mongodb://158.69.126.126", "minehq");
converter.startImport((ignored, error) -> {
if (error != null) {
error.printStackTrace();
}
});*/
}
private void setupConfig() {
try (InputStream in = new FileInputStream("apiv3.properties")) {
config.load(in);
} catch (Exception ex) {
} catch (IOException ex) {
throw new RuntimeException(ex);
}
}
private void setupDatabase() {
ImmutableList<MongoCredential> credentials = ImmutableList.of();
List<MongoCredential> credentials = ImmutableList.of();
if (!config.getProperty("mongo.username").isEmpty()) {
credentials = ImmutableList.of(MongoCredential.createCredential(
config.getProperty("mongo.username"),
config.getProperty("mongo.database"),
config.getProperty("mongo.password").toCharArray()
));
credentials = ImmutableList.of(
MongoCredential.createCredential(
config.getProperty("mongo.username"),
config.getProperty("mongo.database"),
config.getProperty("mongo.password").toCharArray()
)
);
}
MongoClient mongoClient = new MongoClient(new ServerAddress(
config.getProperty("mongo.address"),
Integer.parseInt(config.getProperty("mongo.port"))),
credentials
ConnectionString connectionString = new ConnectionString("mongodb://" + config.getProperty("mongo.address") + ":" + config.getProperty("mongo.port"));
MongoClient mongoClient = MongoClients.create(MongoClientSettings
.builder()
.codecRegistry(createCodecRegistry())
.credentialList(credentials)
.clusterSettings(ClusterSettings.builder()
.applyConnectionString(connectionString)
.build()
)
.build()
);
MorphiaLoggerFactory.reset();
MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class);
database = mongoClient.getDatabase(config.getProperty("mongo.database"));
database.getCollection("auditLog").createIndexes(ImmutableList.of(
new IndexModel(new Document("user", 1)),
new IndexModel(new Document("performedAt", 1)),
new IndexModel(new Document("type", 1))
), (a, b) -> {});
database.getCollection("grants").createIndexes(ImmutableList.of(
new IndexModel(new Document("user", 1)),
new IndexModel(new Document("rank", 1)),
new IndexModel(new Document("addedAt", 1))
), (a, b) -> {});
database.getCollection("ipLog").createIndexes(ImmutableList.of(
new IndexModel(new Document("user", 1)),
new IndexModel(new Document("user", 1).append("userIp", 1))
), (a, b) -> {});
database.getCollection("punishments").createIndexes(ImmutableList.of(
new IndexModel(new Document("user", 1)),
new IndexModel(new Document("type", 1)),
new IndexModel(new Document("addedAt", 1)),
new IndexModel(new Document("addedBy", 1))
), (a, b) -> {});
database.getCollection("users").createIndexes(ImmutableList.of(
new IndexModel(new Document("lastUsername", 1)),
new IndexModel(new Document("emailToken", 1))
), (a, b) -> {});
database.getCollection("userMeta").createIndexes(ImmutableList.of(
new IndexModel(new Document("user", 1).append("serverGroup", 1))
), (a, b) -> {});
}
Morphia morphia = new Morphia();
morphia.mapPackage("net.frozenorb.apiv3.models");
morphia.getMapper().getConverters().addConverter(new UUIDConverter());
private CodecRegistry createCodecRegistry() {
ObjectMapper objectMapper = ObjectMapperFactory.createObjectMapper();
SimpleModule simpleModule = new SimpleModule();
datastore = morphia.createDatastore(mongoClient, config.getProperty("mongo.database"));
datastore.ensureIndexes();
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);
List<CodecProvider> providers = new ArrayList<>();
// Our fixed uuid codec
providers.add(new UUIDCodecProvider());
// Normal providers
providers.add(new ValueCodecProvider());
providers.add(new DocumentCodecProvider());
providers.add(new BsonValueCodecProvider());
// Jackson parser codec
providers.add(new JacksonCodecProvider(objectMapper));
return CodecRegistries.fromProviders(providers);
}
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")))
);
}
private void setupMetrics() {
statsD = new NonBlockingStatsDClient(null, "localhost", 8125);
new Timer("Metrics Task").scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
statsD.recordGaugeValue("apiv3.memory.usage", Runtime.getRuntime().totalMemory() / (1024 * 1024));
statsD.recordGaugeValue("apiv3.memory.max", Runtime.getRuntime().maxMemory() / (1024 * 1024));
}
}, TimeUnit.SECONDS.toMillis(5), TimeUnit.SECONDS.toMillis(5));
}
private void setupBugsnag() {
Client bugsnag = new Client(config.getProperty("bugsnag.apiKey"));
bugsnag.setReleaseStage(config.getProperty("general.releaseStage"));
@ -156,272 +219,108 @@ public final class APIv3 {
bugsnag.setLogger(new BugsnagSLF4JLogger());
}
private void setupHttp() {
ipAddress(config.getProperty("http.address"));
port(Integer.parseInt(config.getProperty("http.port")));
String workerThreads = config.getProperty("http.workerThreads");
// TODO: blockingHandler -> handler
private void setupHttpServer() {
HttpServer webServer = vertx.createHttpServer();
Router mainRouter = Router.router(vertx);
if (!workerThreads.isEmpty()) {
threadPool(Integer.parseInt(workerThreads));
}
before(new MetricsBeforeFilter());
before(new ContentTypeFilter());
before(new ActorAttributeFilter());
before(new AuthorizationFilter());
after(new MetricsAfterFilter());
after(new LoggingFilter());
exception(Exception.class, new LoggingExceptionHandler());
mainRouter.route().handler(new ActorAttributeHandler());
mainRouter.route().handler(new AuthorizationHandler());
mainRouter.route().handler(LoggerHandler.create(LoggerFormat.TINY));
mainRouter.route().method(HttpMethod.PUT).method(HttpMethod.POST).handler(BodyHandler.create());
// TODO: The commented out routes
post("/metrics", new POSTMetrics(), gson::toJson);
get("/announcements", new GETAnnouncements(), gson::toJson);
get("/auditLog", new GETAuditLog(), gson::toJson);
get("/chatFilterList", new GETChatFilterList(), gson::toJson);
get("/dump/:type", new GETDump(), gson::toJson);
get("/whoami", new GETWhoAmI(), gson::toJson);
enableRouteOverview("/routes");
mainRouter.get("/announcements/:id").blockingHandler(new GETAnnouncements());
mainRouter.put("/announcements/:id").blockingHandler(new PUTAnnouncements());
get("/grant/:id", new GETGrant(), gson::toJson);
get("/grants", new GETGrants(), gson::toJson);
delete("/grant/:id", new DELETEGrant(), gson::toJson);
mainRouter.get("/auditLog").blockingHandler(new GETAuditLog());
mainRouter.post("/user/:id/auditLogEntry").blockingHandler(new POSTUserAuditLogEntry());
get("/notificationTemplate/:id", new GETNotificationTemplate(), gson::toJson);
get("/notificationTemplates", new GETNotificationTemplates(), gson::toJson);
post("/notificationTemplate", new POSTNotificationTemplate(), gson::toJson);
//put("/notificationTemplate/:id", new PUTNotificationTemplate(), gson::toJson);
delete("/notificationTemplate/:id", new DELETENotificationTemplate(), gson::toJson);
mainRouter.get("/chatFilterList").handler(new GETChatFilterList());
get("/punishment/:id", new GETPunishment(), gson::toJson);
get("/punishments", new GETPunishments(), gson::toJson);
delete("/punishment/:id", new DELETEPunishment(), gson::toJson);
mainRouter.get("/grant/:id").blockingHandler(new GETGrant());
mainRouter.get("/grants").blockingHandler(new GETGrants());
mainRouter.get("/user/:id/grants").blockingHandler(new GETUserGrants());
mainRouter.post("/user/:id/grant").blockingHandler(new POSTUserGrant());
mainRouter.delete("/grant/:id").blockingHandler(new DELETEGrant());
get("/rank/:id", new GETRank(), gson::toJson);
get("/ranks", new GETRanks(), gson::toJson);
post("/rank", new POSTRank(), gson::toJson);
//put("/rank/:id", new PUTRank(), gson::toJson);
delete("/rank/:id", new DELETERank(), gson::toJson);
mainRouter.get("/ipBan/:id").blockingHandler(new GETIpBan());
mainRouter.get("/ipBans").blockingHandler(new GETIpBans());
mainRouter.get("/ip/:id/ipBans").blockingHandler(new GETIpIpBans());
mainRouter.post("/ip/:id/ipBan").blockingHandler(new POSTIpIpBan());
mainRouter.delete("/ipBan/:id").blockingHandler(new DELETEIpBan());
get("/serverGroup/:id", new GETServerGroup(), gson::toJson);
get("/serverGroups", new GETServerGroups(), gson::toJson);
post("/serverGroup", new POSTServerGroup(), gson::toJson);
//put("/serverGroup/:id", new PUTServerGroup(), gson::toJson);
delete("/serverGroup/:id", new DELETEServerGroup(), gson::toJson);
mainRouter.get("/user/:id/ipLog").blockingHandler(new GETUserIpLog());
get("/server/:id", new GETServer(), gson::toJson);
get("/servers", new GETServers(), gson::toJson);
post("/server/heartbeat", new POSTServerHeartbeat(), gson::toJson);
post("/server", new POSTServer(), gson::toJson);
//put("/server/:id", new PUTServer(), gson::toJson);
delete("/server/:id", new DELETEServer(), gson::toJson);
mainRouter.get("/notificationTemplate/:id").blockingHandler(new GETNotificationTemplate());
mainRouter.get("/notificationTemplates").blockingHandler(new GETNotificationTemplates());
mainRouter.post("/notificationTemplate").blockingHandler(new POSTNotificationTemplate());
//mainRouter.put("/notificationTemplate/:id").blockingHandler(new PUTNotificationTemplate());
mainRouter.delete("/notificationTemplate/:id").blockingHandler(new DELETENotificationTemplate());
get("/staff", new GETStaff(), gson::toJson);
get("/user/:id/details", new GETUserDetails(), gson::toJson);
get("/user/:id/meta/:serverGroup", new GETUserMeta(), gson::toJson);
get("/user/:id/grants", new GETUserGrants(), gson::toJson);
get("/user/:id/punishments", new GETUserPunishments(), gson::toJson);
get("/user/:id/ipLog", new GETUserIPLog(), gson::toJson);
get("/user/:id/requiresTOTP", new GETUserRequiresTOTP(), gson::toJson);
get("/user/:id/verifyPassword", new GETUserVerifyPassword(), gson::toJson);
get("/user/:id", new GETUser(), gson::toJson);
post("/user/:id/verifyTOTP", new POSTUserVerifyTOTP(), gson::toJson);
post("/user/:id/grant", new POSTUserGrant(), gson::toJson);
post("/user/:id/punish", new POSTUserPunish(), gson::toJson);
post("/user/:id/login", new POSTUserLogin(), gson::toJson);
post("/user/:id/leave", new POSTUserLeave(), gson::toJson);
post("/user/:id/notify", new POSTUserNotify(), gson::toJson);
post("/user/:id/register", new POSTUserRegister(), gson::toJson);
post("/user/:id/setupTOTP", new POSTUserSetupTOTP(), gson::toJson);
post("/user/confirmRegister/:emailToken", new POSTUserConfirmRegister(), gson::toJson);
put("/user/:id/meta/:serverGroup", new PUTUserMeta(), gson::toJson);
delete("/user/:id/meta/:serverGroup", new DELETEUserMeta(), gson::toJson);
delete("/user/:id/punishment", new DELETEUserPunishment(), gson::toJson);
mainRouter.get("/punishment/:id").blockingHandler(new GETPunishment());
mainRouter.get("/punishments").blockingHandler(new GETPunishments());
mainRouter.get("/user/:id/punishments").blockingHandler(new GETUserPunishments());
mainRouter.post("/user/:id/punish").blockingHandler(new POSTUserPunish());
mainRouter.delete("/punishment/:id").blockingHandler(new DELETEPunishment());
mainRouter.delete("/user/:id/punishment").blockingHandler(new DELETEUserPunishment());
// There's no way to do a JSON 404 page w/o doing this :(
get("/*", new NotFound(), gson::toJson);
post("/*", new NotFound(), gson::toJson);
put("/*", new NotFound(), gson::toJson);
delete("/*", new NotFound(), gson::toJson);
mainRouter.get("/rank/:id").handler(new GETRank());
mainRouter.get("/ranks").handler(new GETRanks());
mainRouter.post("/rank").blockingHandler(new POSTRank());
//mainRouter.put("/rank/:id").blockingHandler(new PUTRank());
mainRouter.delete("/rank/:id").blockingHandler(new DELETERank());
mainRouter.get("/serverGroup/:id").handler(new GETServerGroup());
mainRouter.get("/serverGroups").handler(new GETServerGroups());
mainRouter.post("/serverGroup").blockingHandler(new POSTServerGroup());
//mainRouter.put("/serverGroup/:id").blockingHandler(new PUTServerGroup());
mainRouter.delete("/serverGroup/:id").blockingHandler(new DELETEServerGroup());
mainRouter.get("/server/:id").handler(new GETServer());
mainRouter.get("/servers").handler(new GETServers());
mainRouter.post("/server/heartbeat").handler(new POSTServerHeartbeat());
mainRouter.post("/server").blockingHandler(new POSTServer());
//mainRouter.put("/server/:id").blockingHandler(new PUTServer());
mainRouter.delete("/server/:id").blockingHandler(new DELETEServer());
mainRouter.get("/staff").blockingHandler(new GETStaff());
mainRouter.get("/user/:id").blockingHandler(new GETUser());
mainRouter.get("/user/:id/details").blockingHandler(new GETUserDetails());
mainRouter.get("/user/:id/meta/:serverGroup").blockingHandler(new GETUserMeta());
mainRouter.get("/user/:id/requiresTOTP").blockingHandler(new GETUserRequiresTOTP());
mainRouter.get("/user/:id/verifyPassword").blockingHandler(new GETUserVerifyPassword());
mainRouter.post("/user/confirmRegister/:emailToken").blockingHandler(new POSTUserConfirmRegister());
mainRouter.post("/user/:id/leave").handler(new POSTUserLeave());
mainRouter.post("/user/:id/login").handler(new POSTUserLogin());
mainRouter.post("/user/:id/notify").blockingHandler(new POSTUserNotify());
mainRouter.post("/user/:id/register").blockingHandler(new POSTUserRegister());
mainRouter.post("/user/:id/setupTOTP").blockingHandler(new POSTUserSetupTOTP());
mainRouter.post("/user/:id/verifyTOTP").blockingHandler(new POSTUserVerifyTOTP());
mainRouter.put("/user/:id/meta/:serverGroup").blockingHandler(new PUTUserMeta());
mainRouter.delete("/user/:id/meta/:serverGroup").blockingHandler(new DELETEUserMeta());
mainRouter.get("/dump/:type").handler(new GETDump());
mainRouter.get("/whoami").handler(new GETWhoAmI());
mainRouter.post("/metrics").blockingHandler(new POSTMetrics());
int port = Integer.parseInt(config.getProperty("http.port"));
webServer.requestHandler(mainRouter::accept).listen(port);
}
private void convertData(String oldIp, boolean forReal) {
// A lot of unneeded .toString()'s and cloning objects is our ghetto null validation.
MongoDatabase importFrom = new MongoClient(oldIp).getDatabase("minehq");
Map<ObjectId, UUID> mongoIdToUUID = new HashMap<>();
AtomicInteger skippedUsers = new AtomicInteger();
AtomicInteger skippedPunishments = new AtomicInteger();
AtomicInteger skippedGrants = new AtomicInteger();
AtomicInteger skippedIpLogs = new AtomicInteger();
private void setupHttpClient() {
httpClient = vertx.createHttpClient();
}
importFrom.getCollection("user").find().forEach(new Block<Document>() {
public static void respondJson(RoutingContext ctx, Object response) {
respondJson(ctx, 200, response);
}
@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) {
APIv3.getDatastore().save(created);
}
log.info("Created user " + created.getLastUsername() + " (" + created.getId() + ")");
}
});
importFrom.getCollection("punishment").find().forEach(new Block<Document>() {
@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;
}
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) {
APIv3.getDatastore().save(created);
}
log.info("Created punishment " + created.getId() + " (" + created.getType() + ")");
}
});
importFrom.getCollection("grant").find().forEach(new Block<Document>() {
@Override
public void apply(Document grant) {
UUID target = mongoIdToUUID.get(((DBRef) grant.get("target")).getId());
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) {
APIv3.getDatastore().save(created);
}
log.info("Created grant " + created.getId() + " (" + created.getRank() + ")");
}
});
importFrom.getCollection("iplog").find().forEach(new Block<Document>() {
@Override
public void apply(Document ipLogEntry) {
UUID user = mongoIdToUUID.get(((DBRef) ipLogEntry.get("user")).getId());
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) {
APIv3.getDatastore().save(created);
}
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");
public static void respondJson(RoutingContext ctx, int code, Object response) {
ctx.response().putHeader(HttpHeaders.CONTENT_TYPE, MediaType.JSON_UTF_8.toString());
ctx.response().setStatusCode(code);
ctx.response().end(gson.toJson(response));
}
}

View File

@ -1,9 +1,12 @@
package net.frozenorb.apiv3;
import io.vertx.core.Vertx;
final class Main {
public static void main(String[] args) {
new APIv3();
System.setProperty("vertx.logger-delegate-factory-class-name", "io.vertx.core.logging.SLF4JLogDelegateFactory");
Vertx.vertx().deployVerticle(new APIv3());
}
}

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.actors;
package net.frozenorb.apiv3.actor;
public interface Actor {

View File

@ -0,0 +1,7 @@
package net.frozenorb.apiv3.actor;
public enum ActorType {
WEBSITE, BUNGEE, SERVER, USER, UNKNOWN
}

View File

@ -0,0 +1,23 @@
package net.frozenorb.apiv3.actor.actors;
import net.frozenorb.apiv3.actor.Actor;
import net.frozenorb.apiv3.actor.ActorType;
public final class BungeeActor implements Actor {
@Override
public boolean isAuthorized() {
return true;
}
@Override
public String getName() {
return "Bungee";
}
@Override
public ActorType getType() {
return ActorType.BUNGEE;
}
}

View File

@ -1,7 +1,9 @@
package net.frozenorb.apiv3.actors;
package net.frozenorb.apiv3.actor.actors;
import lombok.Getter;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.actor.Actor;
import net.frozenorb.apiv3.actor.ActorType;
import net.frozenorb.apiv3.model.Server;
public final class ServerActor implements Actor {
@ -11,14 +13,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

@ -1,15 +1,21 @@
package net.frozenorb.apiv3.actors;
package net.frozenorb.apiv3.actor.actors;
import net.frozenorb.apiv3.actor.Actor;
import net.frozenorb.apiv3.actor.ActorType;
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

@ -0,0 +1,43 @@
package net.frozenorb.apiv3.actor.actors;
import lombok.Getter;
import net.frozenorb.apiv3.actor.Actor;
import net.frozenorb.apiv3.actor.ActorType;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.unsorted.Permissions;
public final class UserActor implements Actor {
@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 {
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

@ -1,15 +1,21 @@
package net.frozenorb.apiv3.actors;
package net.frozenorb.apiv3.actor.actors;
import net.frozenorb.apiv3.actor.Actor;
import net.frozenorb.apiv3.actor.ActorType;
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,7 +0,0 @@
package net.frozenorb.apiv3.actors;
public enum ActorType {
WEBSITE, BUNGEECORD, SERVER, USER, UNKNOWN
}

View File

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

View File

@ -1,40 +0,0 @@
package net.frozenorb.apiv3.actors;
import com.google.common.collect.ImmutableSet;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
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;
private Boolean cachedAuthorized = null;
public UserActor(User user) {
this.user = user;
}
public boolean isAuthorized() {
if (cachedAuthorized != null) {
return cachedAuthorized;
} else {
String highestRankId = user.getHighestRank().getId();
cachedAuthorized = permittedUserRanks.contains(highestRankId.toLowerCase());
return cachedAuthorized;
}
}
public String getName() {
return user.getLastUsername();
}
public ActorType getType() {
return ActorType.USER;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,59 @@
package net.frozenorb.apiv3.dataImport.converters;
import com.google.common.collect.ImmutableMap;
import com.mongodb.Block;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.util.UUIDUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.Map;
import java.util.UUID;
@Slf4j
public final class UserConverter implements Block<Document> {
private final Map<ObjectId, UUID> oidToUniqueId;
public UserConverter(Map<ObjectId, UUID> oidToUniqueId) {
this.oidToUniqueId = oidToUniqueId;
}
@Override
public void apply(Document user) {
String uuidString = String.valueOf(user.get("uuid"));
if (uuidString == null || uuidString.length() != 32 || user.get("name") == null) {
return;
}
UUID uuid = UUID.fromString(uuidString.replaceFirst("([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]+)", "$1-$2-$3-$4-$5"));
if (!UUIDUtils.isAcceptableUUID(uuid)) {
return;
}
oidToUniqueId.put(user.getObjectId("_id"), uuid);
User created = new User(
uuid,
user.get("name").toString(),
ImmutableMap.of(user.get("name").toString(), user.getDate("joined")),
null,
null,
null,
null,
user.getString("email"),
user.getString("phone"),
"INVALID",
user.getDate("joined"),
user.getDate("joined"),
false
);
created.insert();
log.info("Created user " + created.getLastUsername() + " (" + created.getId() + ")");
}
}

View File

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

View File

@ -1,23 +0,0 @@
package net.frozenorb.apiv3.filters;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Filter;
import spark.Request;
import spark.Response;
import spark.Spark;
public final class AuthorizationFilter implements Filter {
public void handle(Request req, Response res) {
Actor actor = req.attribute("actor");
if (!actor.isAuthorized()) {
APIv3.getStatsD().incrementCounter("apiv3.http.unauthorized");
res.header("WWW-Authenticate", "Basic realm=\"MineHQ\"");
Spark.halt(401, APIv3.getGson().toJson(ErrorUtils.error("Unauthorized access: Please authorize as an approved actor. You're currently authorized as " + actor.getName())));
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,128 @@
package net.frozenorb.apiv3.handler;
import com.google.common.base.Charsets;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actor.actors.*;
import net.frozenorb.apiv3.model.Server;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.util.ErrorUtils;
import java.nio.charset.Charset;
import java.util.Base64;
public final class ActorAttributeHandler implements Handler<RoutingContext> {
@Override
public void handle(RoutingContext ctx) {
String authorizationHeader = ctx.request().getHeader("Authorization");
String mhqAuthorizationHeader = ctx.request().getHeader("MHQ-Authorization");
if (authorizationHeader != null) {
processBasicAuthorization(authorizationHeader, ctx);
} else if (mhqAuthorizationHeader != null) {
processMHQAuthorization(mhqAuthorizationHeader, ctx);
} else {
processNoAuthorization(ctx);
}
}
private void processBasicAuthorization(String authHeader, RoutingContext ctx) {
String encodedHeader = authHeader.substring("Basic ".length());
String decodedHeader = new String(Base64.getDecoder().decode(encodedHeader.getBytes(Charsets.UTF_8)), Charsets.UTF_8);
String[] splitHeader = decodedHeader.split(":");
if (splitHeader.length != 2) {
ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize.");
return;
}
String username = splitHeader[0];
String password = splitHeader[1];
User.findByLastUsername(username, (user, error) -> {
if (error != null) {
ErrorUtils.respondInternalError(ctx, error);
return;
}
if (user != null && user.checkPassword(password)) {
ctx.put("actor", new UserActor(user));
ctx.next();
} else {
ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize as " + username + ".");
}
});
}
private void processMHQAuthorization(String authHeader, RoutingContext ctx) {
String[] splitHeader = authHeader.split(" ");
if (splitHeader.length < 2) {
ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize.");
return;
}
String type = splitHeader[0];
switch (type.toLowerCase()) {
case "website":
String givenWebsiteKey = splitHeader[1];
String properWebsiteKey = APIv3.getConfig().getProperty("auth.websiteApiKey");
if (givenWebsiteKey.equals(properWebsiteKey)) {
ctx.put("actor", new WebsiteActor());
ctx.next();
} else {
ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize as website.");
}
break;
case "server":
Server server = Server.findById(splitHeader[1]);
if (server == null) {
ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize: Server " + splitHeader[1] + " not found");
return;
}
if (splitHeader.length != 3) {
ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize: Key not provided");
return;
}
String givenServerKey = splitHeader[2];
if (givenServerKey.equals(server.getApiKey())) {
ctx.put("actor", new ServerActor(server));
ctx.next();
} else {
ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize as " + server.getId() + ".");
}
break;
case "bungee":
String givenBungeeKey = splitHeader[1];
String properBungeeKey = APIv3.getConfig().getProperty("auth.bungeeApiKey");
if (givenBungeeKey.equals(properBungeeKey)) {
ctx.put("actor", new BungeeActor());
ctx.next();
} else {
ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize as bungee.");
}
break;
default:
ErrorUtils.respondGeneric(ctx, 401, "Failed to authorize as " + type + ".");
break;
}
}
public void processNoAuthorization(RoutingContext ctx) {
ctx.put("actor", new UnknownActor());
ctx.next();
}
}

View File

@ -0,0 +1,37 @@
package net.frozenorb.apiv3.handler;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.actor.Actor;
import net.frozenorb.apiv3.actor.ActorType;
import net.frozenorb.apiv3.actor.actors.ServerActor;
import net.frozenorb.apiv3.model.Server;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class AuthorizationHandler implements Handler<RoutingContext> {
@Override
public void handle(RoutingContext ctx) {
Actor actor = ctx.get("actor");
if (!actor.isAuthorized()) {
ErrorUtils.respondGeneric(ctx, 403, "Please authorize as an approved actor. You're currently authorized as " + actor.getName() + " (" + actor.getType() + ")");
return;
}
if (actor.getType() == ActorType.SERVER) {
Server server = ((ServerActor) actor).getServer();
String[] serverAddress = server.getServerIp().split(":");
String expectedHost = serverAddress[0];
String remoteHost = ctx.request().remoteAddress().host();
if (!expectedHost.equals(remoteHost)) {
ErrorUtils.respondGeneric(ctx, 403, "Failed to authorize: Cannot authorize as " + server.getId() + " from given ip.");
return;
}
}
ctx.next();
}
}

View File

@ -0,0 +1,90 @@
package net.frozenorb.apiv3.model;
import com.google.common.collect.ImmutableMap;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actor.Actor;
import net.frozenorb.apiv3.actor.ActorType;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.util.SyncUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.*;
@Entity
public final class AuditLogEntry {
private static final MongoCollection<AuditLogEntry> auditLogCollection = APIv3.getDatabase().getCollection("auditLog", AuditLogEntry.class);
@Getter @Id private String id;
@Getter private UUID user;
@Getter private String userIp;
@Getter private Date performedAt;
@Getter private String actorName;
@Getter private ActorType actorType;
@Getter private AuditLogActionType type;
@Getter private Map<String, Object> metadata;
public static List<AuditLogEntry> findAllSync() {
return SyncUtils.blockMulti(auditLogCollection.find().sort(new Document("performedAt", -1)));
}
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> findByUserSync(User user) {
return findByUserSync(user.getId());
}
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().sort(new Document("performedAt", -1)).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) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.userIp = userIp;
this.performedAt = new Date();
this.actorName = actor.getName();
this.actorType = actor.getType();
this.type = type;
this.metadata = ImmutableMap.copyOf(metadata);
}
public void insert(SingleResultCallback<Void> callback) {
auditLogCollection.insertOne(this, callback);
}
}

View File

@ -0,0 +1,163 @@
package net.frozenorb.apiv3.model;
import com.google.common.collect.Collections2;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.util.SyncUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.*;
import java.util.stream.Collectors;
@Entity
@AllArgsConstructor
public final class Grant {
private static final MongoCollection<Grant> grantsCollection = APIv3.getDatabase().getCollection("grants", Grant.class);
@Getter @Id private String id;
@Getter private UUID user;
@Getter private String reason;
@Getter private Set<String> scopes = new HashSet<>(); // So on things w/o scopes we still load properly (Morphia drops empty sets)
@Getter private String rank;
@Getter private Date expiresAt;
@Getter private UUID addedBy;
@Getter private Date addedAt;
@Getter private UUID removedBy;
@Getter private Date removedAt;
@Getter private String removalReason;
public static List<Grant> findAllSync() {
return SyncUtils.blockMulti(grantsCollection.find().sort(new Document("addedAt", -1)));
}
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(Collection<Rank> ranks) {
Collection<String> convertedRanks = ranks.stream().map(Rank::getId).collect(Collectors.toList());
return SyncUtils.blockMulti(grantsCollection.find(new Document("rank", new Document("$in", convertedRanks))));
}
public static Grant findByIdSync(String id) {
return SyncUtils.blockOne(grantsCollection.find(new Document("_id", id)));
}
public static List<Grant> findByUserSync(User user) {
return findByUserSync(user.getId());
}
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().sort(new Document("addedAt", -1)).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(Collection<Rank> ranks, SingleResultCallback<List<Grant>> callback) {
Collection<String> convertedRanks = ranks.stream().map(Rank::getId).collect(Collectors.toList());
grantsCollection.find(new Document("rank", new Document("$in", convertedRanks))).into(new ArrayList<>(), callback);
}
public static void findById(String id, SingleResultCallback<Grant> callback) {
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) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.reason = reason;
this.scopes = new HashSet<>(Collections2.transform(scopes, ServerGroup::getId));
this.rank = rank.getId();
this.expiresAt = expiresAt;
this.addedBy = addedBy == null ? null : addedBy.getId();
this.addedAt = new Date();
}
public boolean isActive() {
return !(isExpired() || isRemoved());
}
public boolean isExpired() {
if (expiresAt == null) {
return false; // Never expires
} else {
return expiresAt.before(new Date());
}
}
public boolean isRemoved() {
return removedBy != null;
}
public boolean appliesOn(ServerGroup serverGroup) {
return isGlobal() || scopes.contains(serverGroup.getId());
}
public boolean isGlobal() {
return scopes.isEmpty();
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
grantsCollection.insertOne(this, callback);
callback.get();
}
public void delete(User removedBy, String reason) {
this.removedBy = removedBy.getId();
this.removedAt = new Date();
this.removalReason = reason;
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
grantsCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
}

View File

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

View File

@ -0,0 +1,110 @@
package net.frozenorb.apiv3.model;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.util.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 {
private static final MongoCollection<IpLogEntry> ipLogCollection = APIv3.getDatabase().getCollection("ipLog", IpLogEntry.class);
@Getter @Id private String id;
@Getter private UUID user;
@Getter private String userIp;
@Getter private Date firstSeenAt;
@Getter private Date lastSeenAt;
@Getter private int uses;
public static List<IpLogEntry> findAllSync() {
return SyncUtils.blockMulti(ipLogCollection.find().sort(new Document("lastSeenAt", -1)));
}
public static IpLogEntry findByIdSync(String id) {
return SyncUtils.blockOne(ipLogCollection.find(new Document("_id", id)));
}
public static List<IpLogEntry> findByUserSync(User user) {
return findByUserSync(user.getId());
}
public static List<IpLogEntry> findByUserSync(UUID user) {
return SyncUtils.blockMulti(ipLogCollection.find(new Document("user", user)).sort(new Document("lastSeenAt", -1)));
}
public static IpLogEntry findByUserAndIpSync(User user, String userIp) {
return findByUserAndIpSync(user.getId(), 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().sort(new Document("lastSeenAt", -1)).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)).sort(new Document("lastSeenAt", -1)).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) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.userIp = userIp;
this.firstSeenAt = new Date();
this.lastSeenAt = new Date();
this.uses = 0;
}
public void used() {
this.lastSeenAt = new Date();
this.uses++;
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
ipLogCollection.insertOne(this, callback);
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
ipLogCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
}

View File

@ -0,0 +1,90 @@
package net.frozenorb.apiv3.model;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.DeleteResult;
import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.util.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);
@Getter @Id private String id;
@Getter @Setter private String subject;
@Getter @Setter private String body;
public static List<NotificationTemplate> findAllSync() {
return SyncUtils.blockMulti(notificationTemplatesCollection.find());
}
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) {
this.id = id;
this.subject = subject;
this.body = body;
}
public String fillSubject(Map<String, Object> replacements) {
return fill(subject, replacements);
}
public String fillBody(Map<String, Object> replacements) {
return fill(body, replacements);
}
private String fill(String working, Map<String, Object> replacements) {
for (Map.Entry<String, Object> replacement : replacements.entrySet()) {
String key = replacement.getKey();
String value = String.valueOf(replacement.getValue());
working = working.replace("%" + key + "%", value);
}
return working;
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
notificationTemplatesCollection.insertOne(this, callback);
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
notificationTemplatesCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
public void delete() {
BlockingCallback<DeleteResult> callback = new BlockingCallback<>();
notificationTemplatesCollection.deleteOne(new Document("_id", id), callback);
callback.get();
}
}

View File

@ -0,0 +1,217 @@
package net.frozenorb.apiv3.model;
import com.mongodb.async.SingleResultCallback;
import com.mongodb.async.client.MongoCollection;
import com.mongodb.client.result.UpdateResult;
import fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actor.Actor;
import net.frozenorb.apiv3.actor.ActorType;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.util.SyncUtils;
import net.frozenorb.apiv3.util.TimeUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.*;
import java.util.stream.Collectors;
@Entity
@AllArgsConstructor
public final class Punishment {
private static final MongoCollection<Punishment> punishmentsCollection = APIv3.getDatabase().getCollection("punishments", Punishment.class);
@Getter @Id private String id;
@Getter private UUID user;
@Getter private String reason;
@Getter private PunishmentType type;
@Getter private Date expiresAt;
@Getter private Map<String, Object> metadata;
@Getter private String linkedIpBanId;
@Getter private UUID addedBy;
@Getter private Date addedAt;
@Getter private String actorName;
@Getter private ActorType actorType;
@Getter private UUID removedBy;
@Getter private Date removedAt;
@Getter private String removalReason;
public static List<Punishment> findAllSync() {
return SyncUtils.blockMulti(punishmentsCollection.find().sort(new Document("addedAt", -1)));
}
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(Collection<PunishmentType> types) {
Collection<String> convertedTypes = types.stream().map(PunishmentType::name).collect(Collectors.toList());
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("type", new Document("$in", convertedTypes))));
}
public static Punishment findByIdSync(String id) {
return SyncUtils.blockOne(punishmentsCollection.find(new Document("_id", id)));
}
public static List<Punishment> findByUserSync(User user) {
return findByUserSync(user.getId());
}
public static List<Punishment> findByUserSync(UUID user) {
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("user", user)));
}
public static List<Punishment> findByUserAndTypeSync(User user, Collection<PunishmentType> types) {
return findByUserAndTypeSync(user.getId(), types);
}
public static List<Punishment> findByUserAndTypeSync(UUID user, Collection<PunishmentType> types) {
Collection<String> convertedTypes = types.stream().map(PunishmentType::name).collect(Collectors.toList());
return SyncUtils.blockMulti(punishmentsCollection.find(new Document("user", user).append("type", new Document("$in", convertedTypes))));
}
public static void findAll(SingleResultCallback<List<Punishment>> callback) {
punishmentsCollection.find().sort(new Document("addedAt", -1)).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(Collection<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) {
Collection<String> convertedTypes = types.stream().map(PunishmentType::name).collect(Collectors.toList());
punishmentsCollection.find(new Document("type", new Document("$in", convertedTypes))).into(new ArrayList<>(), callback);
}
public static void findById(String id, SingleResultCallback<Punishment> callback) {
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, Collection<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) {
findByUserAndType(user.getId(), types, callback);
}
public static void findByUserAndType(UUID user, Collection<PunishmentType> types, SingleResultCallback<List<Punishment>> callback) {
Collection<String> convertedTypes = types.stream().map(PunishmentType::name).collect(Collectors.toList());
punishmentsCollection.find(new Document("user", user).append("type", new Document("$in", convertedTypes))).into(new ArrayList<>(), callback);
}
public Punishment() {} // For Morphia
public Punishment(User user, String reason, PunishmentType type, Date expiresAt, User addedBy, Actor actor, Map<String, Object> metadata) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.reason = reason;
this.type = type;
this.expiresAt = expiresAt;
this.addedBy = addedBy == null ? null : addedBy.getId();
this.addedAt = new Date();
this.actorName = actor.getName();
this.actorType = actor.getType();
this.metadata = metadata;
}
public boolean isActive() {
return !(isExpired() || isRemoved());
}
public boolean isExpired() {
if (expiresAt == null) {
return false; // Never expires
} else {
return expiresAt.before(new Date());
}
}
public boolean isRemoved() {
return removedBy != null;
}
public String getAccessDenialReason() {
switch (type) {
case BLACKLIST:
return "Your account has been blacklisted from the MineHQ Network. \n\nThis type of punishment cannot be appealed.";
case BAN:
String accessDenialReason = "Your account has been suspended from the MineHQ Network. \n\n";
if (getExpiresAt() != null) {
accessDenialReason += "Expires in " + TimeUtils.formatIntoDetailedString(TimeUtils.getSecondsBetween(getExpiresAt(), new Date()));
} else {
accessDenialReason += "Appeal at MineHQ.com/appeal";
}
return accessDenialReason;
default:
return null;
}
}
public void linkIpBan(IpBan ipBan) {
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
punishmentsCollection.insertOne(this, callback);
callback.get();
}
public void delete(User removedBy, String reason) {
this.removedBy = removedBy.getId();
this.removedAt = new Date();
this.removalReason = reason;
if (linkedIpBanId != null) {
IpBan ipBan = IpBan.findByIdSync(linkedIpBanId);
if (ipBan != null && ipBan.isActive()) {
ipBan.delete(removedBy, "Linked punishment removed: " + reason);
}
}
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
punishmentsCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
public enum PunishmentType {
BLACKLIST, BAN, MUTE, WARN
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,417 @@
package net.frozenorb.apiv3.model;
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 fr.javatic.mongo.jacksonCodec.Entity;
import fr.javatic.mongo.jacksonCodec.objectId.Id;
import io.vertx.core.CompositeFuture;
import io.vertx.core.Future;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.gson.ExcludeFromReplies;
import net.frozenorb.apiv3.serialization.jackson.UUIDJsonDeserializer;
import net.frozenorb.apiv3.serialization.jackson.UUIDJsonSerializer;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.util.MojangUtils;
import net.frozenorb.apiv3.util.PermissionUtils;
import net.frozenorb.apiv3.util.SyncUtils;
import net.frozenorb.apiv3.util.UUIDUtils;
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 @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;
@Getter @ExcludeFromReplies @Setter private String emailToken;
@Getter @ExcludeFromReplies @Setter private Date emailTokenSetAt;
@Getter @ExcludeFromReplies private String password;
@Getter @Setter private String email;
@Getter private String phoneNumber;
@Getter private String lastSeenOn;
@Getter private Date lastSeenAt;
@Getter private Date firstSeenAt;
@Getter private boolean online;
public static List<User> findAllSync() {
return SyncUtils.blockMulti(usersCollection.find().sort(new Document("lastSeenAt", -1)));
}
public static User findByIdSync(String id) {
UUID uuid;
try {
uuid = UUID.fromString(id);
} catch (IllegalArgumentException ex) {
return null;
}
return findByIdSync(uuid);
}
public static User findByIdSync(UUID id) {
if (UUIDUtils.isAcceptableUUID(id)) {
return SyncUtils.blockOne(usersCollection.find(new Document("_id", id)));
} else {
return null;
}
}
public static User findByEmailTokenSync(String emailToken) {
return SyncUtils.blockOne(usersCollection.find(new Document("emailToken", emailToken)));
}
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().sort(new Document("lastSeenAt", -1)).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) {
this.id = id;
this.lastUsername = ""; // Intentional, so updateUsername actually does something.
this.aliases = new HashMap<>();
this.totpSecret = null;
this.password = null;
this.email = null;
this.phoneNumber = null;
this.lastSeenOn = null;
this.lastSeenAt = new Date();
this.firstSeenAt = new Date();
// TODO: MAKE THIS ASYNC? SOMEHOW?
BlockingCallback<Void> blockingCallback = new BlockingCallback<>();
updateUsername(lastUsername, blockingCallback);
blockingCallback.get();
}
public boolean hasPermissionAnywhere(String permission) {
Map<String, Boolean> globalPermissions = PermissionUtils.getDefaultPermissions(getHighestRankAnywhere());
for (Map.Entry<ServerGroup, Rank> serverGroupEntry : getHighestRanks().entrySet()) {
ServerGroup serverGroup = serverGroupEntry.getKey();
Rank rank = serverGroupEntry.getValue();
globalPermissions = PermissionUtils.mergePermissions(
globalPermissions,
serverGroup.calculatePermissions(rank)
);
}
return globalPermissions.containsKey(permission) && globalPermissions.get(permission);
}
// TODO: Clean
public boolean seenOnServer(Server server) {
if (online && server.getId().equals(this.lastSeenOn)) {
return false;
}
this.lastSeenOn = server.getId();
if (!online) {
this.lastSeenAt = new Date();
}
this.online = true;
return true;
}
public void leftServer() {
this.lastSeenAt = new Date();
this.online = false;
}
public void updateUsername(String newUsername, SingleResultCallback<Void> callback) {
this.aliases.put(newUsername, new Date());
if (newUsername.equalsIgnoreCase(lastUsername)) {
callback.onResult(null, null);
return;
}
this.lastUsername = newUsername;
User.findByLastUsername(newUsername, (otherUser, error) -> {
if (error != null) {
callback.onResult(null, error);
} else if (otherUser != null) {
MojangUtils.getName(otherUser.getId(), (newName, error2) -> {
if (error2 != null) {
callback.onResult(null, error2);
} else {
otherUser.updateUsername(newName, callback);
}
});
} else {
callback.onResult(null, null);
}
});
}
public void setPassword(String input) {
this.password = Hashing
.sha256()
.hashString(input + "$" + id.toString(), Charsets.UTF_8)
.toString();
}
public boolean checkPassword(String input) {
String hashed = Hashing
.sha256()
.hashString(input + "$" + id.toString(), Charsets.UTF_8)
.toString();
return password != null && hashed.equals(password);
}
public Rank getHighestRankAnywhere() {
return getHighestRankScoped(null);
}
public Rank getHighestRankScoped(ServerGroup serverGroup) {
return getHighestRankScoped(serverGroup, Grant.findByUserSync(this));
}
// TODO: Clean
// This is only used to help batch requests to mongo
public Rank getHighestRankScoped(ServerGroup serverGroup, Iterable<Grant> grants) {
Rank highest = null;
for (Grant grant : grants) {
if (!grant.isActive() || (serverGroup != null && !grant.appliesOn(serverGroup))) {
continue;
}
Rank rank = Rank.findById(grant.getRank());
if (highest == null || rank.getWeight() > highest.getWeight()) {
highest = rank;
}
}
if (highest != null) {
return highest;
} else {
return Rank.findById("default");
}
}
// TODO: Clean
public Map<ServerGroup, Rank> getHighestRanks() {
Map<ServerGroup, Rank> highestRanks = new HashMap<>();
Rank defaultRank = Rank.findById("default");
List<Grant> userGrants = Grant.findByUserSync(this);
for (ServerGroup serverGroup : ServerGroup.findAll()) {
Rank highest = defaultRank;
for (Grant grant : userGrants) {
if (!grant.isActive() || !grant.appliesOn(serverGroup)) {
continue;
}
Rank rank = Rank.findById(grant.getRank());
if (highest == null || rank.getWeight() > highest.getWeight()) {
highest = rank;
}
}
highestRanks.put(serverGroup, highest);
}
return highestRanks;
}
public void getLoginInfo(Server server, String userIp, SingleResultCallback<Map<String, Object>> callback) {
Future<Iterable<Punishment>> punishmentsFuture = Future.future();
Future<Iterable<IpBan>> ipBansFuture = Future.future();
Future<Iterable<Grant>> grantsFuture = Future.future();
Punishment.findByUserAndType(this, ImmutableSet.of(
Punishment.PunishmentType.BLACKLIST,
Punishment.PunishmentType.BAN,
Punishment.PunishmentType.MUTE
), (punishments, error) -> {
if (error != null) {
punishmentsFuture.fail(error);
} else {
punishmentsFuture.complete(punishments);
}
});
if (userIp != null) {
IpBan.findByIp(userIp, (ipBans, error) -> {
if (error != null) {
ipBansFuture.fail(error);
} else {
ipBansFuture.complete(ipBans);
}
});
} else {
ipBansFuture.complete(ImmutableSet.of());
}
Grant.findByUser(this, (grants, error) -> {
if (error != null) {
grantsFuture.fail(error);
} else {
grantsFuture.complete(grants);
}
});
CompositeFuture.all(punishmentsFuture, ipBansFuture, grantsFuture).setHandler((result) -> {
if (result.succeeded()) {
Iterable<Punishment> punishments = result.result().result(0);
Iterable<IpBan> ipBans = result.result().result(1);
Iterable<Grant> grants = result.result().result(2);
callback.onResult(createLoginInfo(server, punishments, ipBans, grants), null);
} else {
callback.onResult(null, result.cause());
}
});
}
// This is only used to help batch requests to mongo
public Map<String, Object> createLoginInfo(Server server, Iterable<Punishment> punishments, Iterable<IpBan> ipBans, Iterable<Grant> grants) {
Punishment activeMute = null;
Punishment activeBan = null;
IpBan activeIpBan = null;
for (Punishment punishment : punishments) {
if (!punishment.isActive()) {
continue;
}
if (punishment.getType() == Punishment.PunishmentType.MUTE) {
activeMute = punishment;
} else if (punishment.getType() == Punishment.PunishmentType.BAN || punishment.getType() == Punishment.PunishmentType.BLACKLIST) {
activeBan = punishment;
}
}
for (IpBan ipBan : ipBans) {
if (ipBan.isActive()) {
activeIpBan = ipBan;
break;
}
}
Rank highestRank = getHighestRankScoped(ServerGroup.findById(server.getServerGroup()), grants);
Map<String, Object> access = ImmutableMap.of(
"allowed", true,
"message", "Public server"
);
if (activeBan != null) {
access = ImmutableMap.of(
"allowed", false,
"message", activeBan.getAccessDenialReason(),
"activeBanId", activeBan.getId()
);
} else if (activeIpBan != null) {
access = ImmutableMap.of(
"allowed", false,
"message", activeIpBan.getAccessDenialReason(),
"activeIpBanId", activeIpBan.getId()
);
}
// Generics are weird, yes we have to do this.
ImmutableMap.Builder<String, Object> result = ImmutableMap.<String, Object>builder()
.put("user", this)
.put("access", access)
.put("rank", highestRank.getId())
.put("totpSetup", getTotpSecret() != null);
if (activeMute != null) {
result.put("mute", activeMute);
}
return result.build();
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
usersCollection.insertOne(this, callback);
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
save(callback);
callback.get();
}
public void save(SingleResultCallback<UpdateResult> callback) {
usersCollection.replaceOne(new Document("_id", id), this, callback);
}
}

View File

@ -0,0 +1,92 @@
package net.frozenorb.apiv3.model;
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 fr.javatic.mongo.jacksonCodec.Entity;
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.util.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);
@Getter @Id private String id;
@Getter private UUID user;
@Getter private String serverGroup;
@Getter @Setter private Map<String, Object> data;
public static List<UserMetaEntry> findAllSync() {
return SyncUtils.blockMulti(userMetaCollection.find());
}
public static UserMetaEntry findByIdSync(String id) {
return SyncUtils.blockOne(userMetaCollection.find(new Document("_id", id)));
}
public static UserMetaEntry findByUserAndGroupSync(User user, ServerGroup serverGroup) {
return findByUserAndGroupSync(user.getId(), 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) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.serverGroup = serverGroup.getId();
this.data = ImmutableMap.copyOf(data);
}
public void insert() {
BlockingCallback<Void> callback = new BlockingCallback<>();
userMetaCollection.insertOne(this, callback);
callback.get();
}
public void save() {
BlockingCallback<UpdateResult> callback = new BlockingCallback<>();
userMetaCollection.replaceOne(new Document("_id", id), this, callback);
callback.get();
}
public void delete() {
BlockingCallback<DeleteResult> callback = new BlockingCallback<>();
userMetaCollection.deleteOne(new Document("_id", id), callback);
callback.get();
}
}

View File

@ -1,42 +0,0 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableMap;
import lombok.Getter;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.Date;
import java.util.Map;
import java.util.UUID;
@Entity(value = "auditLog", noClassnameStored = true)
public final class AuditLogEntry {
@Getter @Id private String id;
@Getter @Indexed private UUID user;
@Getter private String userIp;
@Getter @Indexed private Date performedAt;
@Getter private String actorName;
@Getter private ActorType actorType;
@Getter private AuditLogActionType type;
@Getter private Map<String, Object> metadata;
public AuditLogEntry() {} // For Morphia
public AuditLogEntry(User user, String userIp, Actor actor, AuditLogActionType type, Map<String, Object> metadata) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.userIp = userIp;
this.performedAt = new Date();
this.actorName = actor.getName();
this.actorType = actor.getType();
this.type = type;
this.metadata = ImmutableMap.copyOf(metadata);
}
}

View File

@ -1,101 +0,0 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.Collections2;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.*;
@Entity(value = "grants", noClassnameStored = true)
@AllArgsConstructor
public final class Grant {
@Getter @Id private String id;
@Getter @Indexed private UUID user;
@Getter private String reason;
@Getter private Set<String> scopes = new HashSet<>(); // So on things w/o scopes we still load properly (Morphia drops empty sets)
@Getter @Indexed private String rank;
@Getter private Date expiresAt;
@Getter private UUID addedBy;
@Getter @Indexed private Date addedAt;
@Getter private UUID removedBy;
@Getter private Date removedAt;
@Getter private String removalReason;
public static Grant byId(String id) {
return APIv3.getDatastore().createQuery(Grant.class).field("id").equal(id).get();
}
public static Map<UUID, List<Grant>> byUserGrouped(Iterable<UUID> ids) {
Map<UUID, List<Grant>> result = new HashMap<>();
Set<UUID> uuidsToSearch = new HashSet<>();
for (UUID id : ids) {
result.put(id, new ArrayList<>());
if (UUIDUtils.isAcceptableUUID(id)) {
uuidsToSearch.add(id);
}
}
APIv3.getDatastore().createQuery(Grant.class).field("user").in(uuidsToSearch).forEach((grant) -> {
result.get(grant.getUser()).add(grant);
});
return result;
}
public Grant() {} // For Morphia
public Grant(User user, String reason, Set<ServerGroup> scopes, Rank rank, Date expiresAt, User addedBy) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.reason = reason;
this.scopes = new HashSet<>(Collections2.transform(scopes, ServerGroup::getId));
this.rank = rank.getId();
this.expiresAt = expiresAt;
this.addedBy = addedBy == null ? null : addedBy.getId();
this.addedAt = new Date();
}
public void delete(User removedBy, String reason) {
this.removedBy = removedBy.getId();
this.removedAt = new Date();
this.removalReason = reason;
APIv3.getDatastore().save(this);
}
public boolean isActive() {
return !(isExpired() || isRemoved());
}
public boolean isExpired() {
if (expiresAt == null) {
return false; // Never expires
} else {
return expiresAt.before(new Date());
}
}
public boolean isRemoved() {
return removedBy != null;
}
public boolean appliesOn(ServerGroup serverGroup) {
return isGlobal() || scopes.contains(serverGroup.getId());
}
public boolean isGlobal() {
return scopes.isEmpty();
}
}

View File

@ -1,47 +0,0 @@
package net.frozenorb.apiv3.models;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.*;
import java.util.Date;
import java.util.UUID;
@Entity(value = "ipLog", noClassnameStored = true)
@AllArgsConstructor
@Indexes(
@Index(fields = {
@Field("user"),
@Field("userIp")
})
)
public final class IPLogEntry {
@Getter @Id private String id;
@Getter @Indexed private UUID user;
@Getter @Indexed private String userIp;
@Getter private Date firstSeenAt;
@Getter private Date lastSeenAt;
@Getter private int uses;
public IPLogEntry() {} // For Morphia
public IPLogEntry(User user, String userIp) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.userIp = userIp;
this.firstSeenAt = new Date();
this.lastSeenAt = new Date();
this.uses = 0;
}
public void used() {
this.lastSeenAt = new Date();
this.uses++;
APIv3.getDatastore().save(this);
}
}

View File

@ -1,64 +0,0 @@
package net.frozenorb.apiv3.models;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import java.util.List;
import java.util.Map;
@Entity(value = "notificationTemplates", noClassnameStored = true)
public final class NotificationTemplate {
@Getter @Id private String id;
@Getter private String subject;
@Getter private String body;
public static NotificationTemplate byId(String id) {
return APIv3.getDatastore().createQuery(NotificationTemplate.class).field("id").equal(id).get();
}
public static List<NotificationTemplate> values() {
return APIv3.getDatastore().createQuery(NotificationTemplate.class).asList();
}
public NotificationTemplate() {} // For Morphia
public NotificationTemplate(String id, String subject, String body) {
this.id = id;
this.subject = subject;
this.body = body;
}
public void update(String subject, String body) {
this.subject = subject;
this.body = body;
APIv3.getDatastore().save(this);
}
public void delete() {
APIv3.getDatastore().delete(this);
}
public String fillSubject(Map<String, Object> replacements) {
return fill(subject, replacements);
}
public String fillBody(Map<String, Object> replacements) {
return fill(body, replacements);
}
private String fill(String working, Map<String, Object> replacements) {
for (Map.Entry<String, Object> replacement : replacements.entrySet()) {
String key = replacement.getKey();
String value = String.valueOf(replacement.getValue());
working = working.replace("%" + key + "%", value);
}
return working;
}
}

View File

@ -1,133 +0,0 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableSet;
import lombok.AllArgsConstructor;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.utils.TimeUtils;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.*;
import java.util.*;
@Entity(value = "punishments", noClassnameStored = true)
@AllArgsConstructor
@Indexes(
@Index(fields = {
@Field("user"),
@Field("type")
})
)
public final class Punishment {
@Getter @Id private String id;
@Getter @Indexed private UUID user;
@Getter private String reason;
@Getter @Indexed private PunishmentType type; // Type is indexed for the rank dump
@Getter private Date expiresAt;
@Getter private Map<String, Object> metadata;
@Getter private UUID addedBy;
@Getter @Indexed private Date addedAt;
@Getter private String actorName;
@Getter private ActorType actorType;
@Getter private UUID removedBy;
@Getter private Date removedAt;
@Getter private String removalReason;
public static Punishment byId(String id) {
return APIv3.getDatastore().createQuery(Punishment.class).field("id").equal(id).get();
}
public static Map<UUID, List<Punishment>> byUserGrouped(Iterable<UUID> ids) {
return byUserGrouped(ids, ImmutableSet.copyOf(PunishmentType.values()));
}
public static Map<UUID, List<Punishment>> byUserGrouped(Iterable<UUID> ids, Iterable<Punishment.PunishmentType> types) {
Map<UUID, List<Punishment>> result = new HashMap<>();
Set<UUID> uuidsToSearch = new HashSet<>();
for (UUID id : ids) {
result.put(id, new ArrayList<>());
if (UUIDUtils.isAcceptableUUID(id)) {
uuidsToSearch.add(id);
}
}
APIv3.getDatastore().createQuery(Punishment.class).field("user").in(uuidsToSearch).field("type").in(types).forEach((punishment) -> {
result.get(punishment.getUser()).add(punishment);
});
return result;
}
public Punishment() {} // For Morphia
public Punishment(User user, String reason, PunishmentType type, Date expiresAt, User addedBy, Actor actor, Map<String, Object> metadata) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.reason = reason;
this.type = type;
this.expiresAt = expiresAt;
this.addedBy = addedBy == null ? null : addedBy.getId();
this.addedAt = new Date();
this.actorName = actor.getName();
this.actorType = actor.getType();
this.metadata = metadata;
}
public void delete(User removedBy, String reason) {
this.removedBy = removedBy.getId();
this.removedAt = new Date();
this.removalReason = reason;
APIv3.getDatastore().save(this);
}
public boolean isActive() {
return !(isExpired() || isRemoved());
}
public boolean isExpired() {
if (expiresAt == null) {
return false; // Never expires
} else {
return expiresAt.before(new Date());
}
}
public boolean isRemoved() {
return removedBy != null;
}
public String getAccessDenialReason() {
switch (type) {
case BLACKLIST:
return "Your account has been blacklisted from the MineHQ Network. \n\nThis type of punishment cannot be appealed.";
case BAN:
String accessDenialReason = "Your account has been suspended from the MineHQ Network. \n\n";
if (getExpiresAt() != null) {
accessDenialReason += "Expires in " + TimeUtils.formatIntoDetailedString(TimeUtils.getSecondsBetween(getExpiresAt(), new Date()));
} else {
accessDenialReason += "Appeal at MineHQ.com/appeal";
}
return accessDenialReason;
default:
return null;
}
}
public enum PunishmentType {
BLACKLIST, BAN, MUTE, WARN
}
}

View File

@ -1,326 +0,0 @@
package net.frozenorb.apiv3.models;
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 lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
import net.frozenorb.apiv3.utils.MojangUtils;
import net.frozenorb.apiv3.utils.PermissionUtils;
import net.frozenorb.apiv3.utils.UUIDUtils;
import org.bson.Document;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.*;
@Entity(value = "users", noClassnameStored = true)
@AllArgsConstructor
public final class User {
@Getter @Id private UUID id;
@Getter @Indexed private String lastUsername;
@Getter @ExcludeFromReplies private Map<String, Date> aliases = new HashMap<>();
@Getter @Setter @ExcludeFromReplies private String totpSecret;
@Getter @Indexed @ExcludeFromReplies @Setter private String emailToken;
@Getter @ExcludeFromReplies @Setter private Date emailTokenSetAt;
@Getter @ExcludeFromReplies private String password;
@Getter @Setter private String email;
@Getter private String phoneNumber;
@Getter private String lastSeenOn;
@Getter private Date lastSeenAt;
@Getter private Date firstSeenAt;
@Getter private boolean online;
public static User byId(String id) {
try {
return byId(UUID.fromString(id));
} catch (Exception ex) {
return null;
}
}
public static User byId(UUID id) {
if (UUIDUtils.isAcceptableUUID(id)) {
return APIv3.getDatastore().createQuery(User.class).field("id").equal(id).get();
} else {
return null;
}
}
public static Map<UUID, User> byIdGrouped(Iterable<UUID> ids) {
Map<UUID, User> result = new HashMap<>();
Set<UUID> uuidsToSearch = new HashSet<>();
for (UUID id : ids) {
result.put(id, null);
if (UUIDUtils.isAcceptableUUID(id)) {
uuidsToSearch.add(id);
}
}
APIv3.getDatastore().createQuery(User.class).field("id").in(uuidsToSearch).forEach((user) -> {
result.put(user.getId(), user);
});
return result;
}
public static User byEmailToken(String name) {
return APIv3.getDatastore().createQuery(User.class).field("emailToken").equal(name).get();
}
@Deprecated
public static User byLastUsername(String lastUsername) {
return APIv3.getDatastore().createQuery(User.class).field("lastUsername").equal(lastUsername).get();
}
public User() {} // For Morphia
public User(UUID id, String lastUsername) {
this.id = id;
this.lastUsername = ""; // Intentional, so updateUsername actually does something.
this.aliases = new HashMap<>();
this.totpSecret = null;
this.password = null;
this.email = null;
this.phoneNumber = null;
this.lastSeenOn = null;
this.lastSeenAt = new Date();
this.firstSeenAt = new Date();
updateUsername(lastUsername);
}
public boolean hasPermissionScoped(String permission, ServerGroup scope) {
Rank highestRank = getHighestRank(scope);
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
PermissionUtils.getDefaultPermissions(highestRank),
scope.calculatePermissions(highestRank)
);
return scopedPermissions.containsKey(permission) && scopedPermissions.get(permission);
}
public boolean hasPermissionAnywhere(String permission) {
Map<String, Boolean> globalPermissions = PermissionUtils.getDefaultPermissions(getHighestRank());
for (Map.Entry<ServerGroup, Rank> serverGroupEntry : getHighestRanks().entrySet()) {
ServerGroup serverGroup = serverGroupEntry.getKey();
Rank rank = serverGroupEntry.getValue();
globalPermissions = PermissionUtils.mergePermissions(
globalPermissions,
serverGroup.calculatePermissions(rank)
);
}
return globalPermissions.containsKey(permission) && globalPermissions.get(permission);
}
public List<Grant> getGrants() {
return APIv3.getDatastore().createQuery(Grant.class).field("user").equal(id).asList();
}
public List<IPLogEntry> getIPLog() {
return APIv3.getDatastore().createQuery(IPLogEntry.class).field("user").equal(id).asList();
}
public IPLogEntry getIPLogEntry(String ip) {
IPLogEntry existing = APIv3.getDatastore().createQuery(IPLogEntry.class).field("user").equal(id).field("userIp").equal(ip).get();
if (existing == null) {
existing = new IPLogEntry(this, ip);
APIv3.getDatastore().save(existing);
}
return existing;
}
public List<Punishment> getPunishments() {
return APIv3.getDatastore().createQuery(Punishment.class).field("user").equal(id).asList();
}
public List<Punishment> getPunishments(Iterable<Punishment.PunishmentType> types) {
return APIv3.getDatastore().createQuery(Punishment.class).field("user").equal(id).field("type").in(types).asList();
}
public UserMetaEntry getMeta(ServerGroup group) {
return APIv3.getDatastore().createQuery(UserMetaEntry.class).field("user").equal(id).field("serverGroup").equal(group.getId()).get();
}
public void saveMeta(ServerGroup group, Document data) {
UserMetaEntry entry = getMeta(group);
if (entry == null) {
APIv3.getDatastore().save(new UserMetaEntry(this, group, data));
} else {
entry.setData(data);
APIv3.getDatastore().save(entry);
}
}
public boolean seenOnServer(Server server) {
if (online && server.getId().equals(this.lastSeenOn)) {
return false;
}
this.lastSeenOn = server.getId();
if (!online) {
this.lastSeenAt = new Date();
}
this.online = true;
return true;
}
public void leftServer() {
this.lastSeenAt = new Date();
this.online = false;
}
public void updateUsername(String username) {
if (!username.equals(lastUsername)) {
this.lastUsername = username;
User withNewUsername;
while ((withNewUsername = User.byLastUsername(username)) != null) {
String newUsername = MojangUtils.getName(withNewUsername.getId());
withNewUsername.updateUsername(newUsername);
}
}
this.aliases.put(username, new Date());
APIv3.getDatastore().save(this);
}
public void setPassword(String input) {
this.password = Hashing
.sha256()
.hashString(input + "$" + id.toString(), Charsets.UTF_8)
.toString();
}
public boolean checkPassword(String input) {
String hashed = Hashing
.sha256()
.hashString(input + "$" + id.toString(), Charsets.UTF_8)
.toString();
return hashed.equals(password);
}
public Rank getHighestRank() {
return getHighestRank(null);
}
public Rank getHighestRank(ServerGroup serverGroup) {
return getHighestRank(serverGroup, getGrants());
}
// This is only used to help batch requests to mongo
public Rank getHighestRank(ServerGroup serverGroup, Iterable<Grant> grants) {
Rank highest = null;
for (Grant grant : grants) {
if (!grant.isActive() || (serverGroup != null && !grant.appliesOn(serverGroup))) {
continue;
}
Rank rank = Rank.byId(grant.getRank());
if (highest == null || rank.getWeight() > highest.getWeight()) {
highest = rank;
}
}
if (highest != null) {
return highest;
} else {
return Rank.byId("default");
}
}
public Map<ServerGroup, Rank> getHighestRanks() {
Map<ServerGroup, Rank> highestRanks = new HashMap<>();
Rank defaultRank = Rank.byId("default");
List<Grant> userGrants = getGrants();
for (ServerGroup serverGroup : ServerGroup.values()) {
Rank highest = defaultRank;
for (Grant grant : userGrants) {
if (!grant.isActive() || !grant.appliesOn(serverGroup)) {
continue;
}
Rank rank = Rank.byId(grant.getRank());
if (highest == null || rank.getWeight() > highest.getWeight()) {
highest = rank;
}
}
highestRanks.put(serverGroup, highest);
}
return highestRanks;
}
public Map<String, Object> getLoginInfo(Server server) {
return getLoginInfo(
server,
getPunishments(ImmutableSet.of(
Punishment.PunishmentType.BLACKLIST,
Punishment.PunishmentType.BAN,
Punishment.PunishmentType.MUTE
)),
getGrants()
);
}
// This is only used to help batch requests to mongo
public Map<String, Object> getLoginInfo(Server server, Iterable<Punishment> punishments, Iterable<Grant> grants) {
Punishment activeMute = null;
String accessDenialReason = null;
for (Punishment punishment : punishments) {
if (!punishment.isActive()) {
continue;
}
if (punishment.getType() == Punishment.PunishmentType.MUTE) {
activeMute = punishment;
} else {
accessDenialReason = punishment.getAccessDenialReason();
}
}
Rank highestRank = getHighestRank(ServerGroup.byId(server.getServerGroup()), grants);
// Generics are weird, yes we have to do this.
ImmutableMap.Builder<String, Object> result = ImmutableMap.<String, Object>builder()
.put("user", this)
.put("access", ImmutableMap.of(
"allowed", accessDenialReason == null,
"message", accessDenialReason == null ? "Public server" : accessDenialReason
))
.put("rank", highestRank.getId())
.put("totpSetup", getTotpSecret() != null);
if (activeMute != null) {
result.put("mute", activeMute);
}
return result.build();
}
}

View File

@ -1,36 +0,0 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableMap;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.Map;
import java.util.UUID;
@Entity(value = "userMeta", noClassnameStored = true)
public final class UserMetaEntry {
@Getter @Id private String id;
@Getter @Indexed private UUID user;
@Getter @Indexed private String serverGroup;
@Getter @Setter private Map<String, Object> data;
public UserMetaEntry() {} // For Morphia
public UserMetaEntry(User user, ServerGroup serverGroup, Map<String, Object> data) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.serverGroup = serverGroup.getId();
this.data = ImmutableMap.copyOf(data);
}
public void delete() {
APIv3.getDatastore().delete(this);
}
}

View File

@ -1,25 +1,24 @@
package net.frozenorb.apiv3.routes;
package net.frozenorb.apiv3.route;
import com.google.common.collect.ImmutableSet;
import com.timgroup.statsd.Event;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
import net.frozenorb.apiv3.model.Grant;
import net.frozenorb.apiv3.model.Punishment;
import net.frozenorb.apiv3.util.ErrorUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
public final class GETDump implements Route {
public final class GETDump implements Handler<RoutingContext> {
private List<UUID> banCache = new ArrayList<>();
private List<UUID> blacklistCache = new ArrayList<>();
private Map<String, List<UUID>> grantCache = new HashMap<>();
public GETDump() {
// TODO: Use vertx scheduler
Thread dumpUpdater = new Thread() {
@Override
@ -27,11 +26,11 @@ public final class GETDump implements Route {
int tick = 0;
while (true) {
if (tick == 0 || tick % 2 == 1) {
if (tick == 0 || tick % 2 != 0) {
List<UUID> banCache = new ArrayList<>();
List<UUID> blacklistCache = new ArrayList<>();
APIv3.getDatastore().createQuery(Punishment.class).field("type").in(ImmutableSet.of(
Punishment.findByTypeSync(ImmutableSet.of(
Punishment.PunishmentType.BAN,
Punishment.PunishmentType.BLACKLIST
)).forEach((punishment) -> {
@ -53,7 +52,7 @@ public final class GETDump implements Route {
if (tick == 0 || tick % 2 == 0) {
Map<String, List<UUID>> grantCache = new HashMap<>();
APIv3.getDatastore().createQuery(Grant.class).forEach((grant) -> {
Grant.findAllSync().forEach((grant) -> {
if (grant.isActive()) {
List<UUID> users = grantCache.get(grant.getRank());
@ -71,7 +70,7 @@ public final class GETDump implements Route {
try {
Thread.sleep(TimeUnit.MINUTES.toMillis(3));
} catch (Exception ex) {
} catch (InterruptedException ex) {
throw new RuntimeException(ex);
}
@ -86,25 +85,30 @@ public final class GETDump implements Route {
dumpUpdater.start();
}
public Object handle(Request req, Response res) {
String type = req.params("type");
public void handle(RoutingContext ctx) {
String type = ctx.request().getParam("type");
switch (type.toLowerCase()) {
case "ban":
return banCache;
APIv3.respondJson(ctx, banCache);
return;
case "blacklist":
return blacklistCache;
APIv3.respondJson(ctx, blacklistCache);
return;
case "accessdeniable": // Lowercase d because we convert to lowercase above
List<UUID> result = new ArrayList<>();
result.addAll(banCache);
result.addAll(blacklistCache);
return result;
APIv3.respondJson(ctx, result);
return;
case "grant":
return grantCache;
APIv3.respondJson(ctx, grantCache);
return;
default:
return ErrorUtils.invalidInput(type + " is not a valid type. Not in [ban, blacklist, accessDeniable, grant]");
ErrorUtils.respondInvalidInput(ctx, type + " is not a valid type. Not in [ban, blacklist, accessDeniable, grant]");
return;
}
}

View File

@ -0,0 +1,21 @@
package net.frozenorb.apiv3.route;
import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actor.Actor;
public final class GETWhoAmI implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
Actor actor = ctx.get("actor");
APIv3.respondJson(ctx, ImmutableMap.of(
"name", actor.getName(),
"type", actor.getType(),
"authorized", actor.isAuthorized()
));
}
}

View File

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

View File

@ -0,0 +1,22 @@
package net.frozenorb.apiv3.route.announcements;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.ServerGroup;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class GETAnnouncements implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
ServerGroup serverGroup = ServerGroup.findById(ctx.request().getParam("id"));
if (serverGroup == null) {
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("id"));
return;
}
APIv3.respondJson(ctx, serverGroup.getAnnouncements());
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,23 @@
package net.frozenorb.apiv3.route.grants;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.Grant;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class GETUserGrants implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
APIv3.respondJson(ctx, Grant.findByUserSync(target));
}
}

View File

@ -0,0 +1,77 @@
package net.frozenorb.apiv3.route.grants;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.Grant;
import net.frozenorb.apiv3.model.Rank;
import net.frozenorb.apiv3.model.ServerGroup;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.util.ErrorUtils;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public final class POSTUserGrant implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
String reason = ctx.request().getParam("reason");
if (reason == null || reason.trim().isEmpty()) {
ErrorUtils.respondRequiredInput(ctx, "reason");
return;
}
Set<ServerGroup> scopes = new HashSet<>();
String scopesUnparsed = ctx.request().getParam("scopes");
if (!scopesUnparsed.isEmpty()) {
for (String serverGroupId : scopesUnparsed.split(",")) {
ServerGroup serverGroup = ServerGroup.findById(serverGroupId);
if (serverGroup == null) {
ErrorUtils.respondNotFound(ctx, "Server group", serverGroupId);
return;
}
scopes.add(serverGroup);
}
}
Rank rank = Rank.findById(ctx.request().getParam("rank"));
if (rank == null) {
ErrorUtils.respondNotFound(ctx, "Rank", ctx.request().getParam("rank"));
return;
}
Date expiresAt;
try {
expiresAt = new Date(Long.parseLong(ctx.request().getParam("expiresAt")));
} catch (NumberFormatException ex) {
expiresAt = null;
}
if (expiresAt != null && expiresAt.before(new Date())) {
ErrorUtils.respondInvalidInput(ctx, "Expiration date cannot be in the past.");
return;
}
// We purposely don't do a null check, grants don't have to have a source.
User addedBy = User.findByIdSync(ctx.request().getParam("addedBy"));
Grant grant = new Grant(target, reason, scopes, rank, expiresAt, addedBy);
grant.insert();
APIv3.respondJson(ctx, grant);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,23 @@
package net.frozenorb.apiv3.route.ipLog;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.IpLogEntry;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class GETUserIpLog implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
APIv3.respondJson(ctx, IpLogEntry.findByUserSync(target));
}
}

View File

@ -0,0 +1,23 @@
package net.frozenorb.apiv3.route.notificationTemplates;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.NotificationTemplate;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class DELETENotificationTemplate implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
NotificationTemplate notificationTemplate = NotificationTemplate.findByIdSync(ctx.request().getParam("id"));
if (notificationTemplate == null) {
ErrorUtils.respondNotFound(ctx, "Notification template", ctx.request().getParam("id"));
return;
}
notificationTemplate.delete();
APIv3.respondJson(ctx, notificationTemplate);
}
}

View File

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

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.route.notificationTemplates;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.NotificationTemplate;
public final class GETNotificationTemplates implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
APIv3.respondJson(ctx, NotificationTemplate.findAllSync());
}
}

View File

@ -0,0 +1,20 @@
package net.frozenorb.apiv3.route.notificationTemplates;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.NotificationTemplate;
public final class POSTNotificationTemplate implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
String id = ctx.request().getParam("id");
String subject = ctx.request().getParam("subject");
String body = ctx.request().getParam("body");
NotificationTemplate notificationTemplate = new NotificationTemplate(id, subject, body);
notificationTemplate.insert();
APIv3.respondJson(ctx, notificationTemplate);
}
}

View File

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

View File

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

View File

@ -0,0 +1,55 @@
package net.frozenorb.apiv3.route.punishments;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.model.AuditLogEntry;
import net.frozenorb.apiv3.model.Punishment;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class DELETEUserPunishment implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
Punishment.PunishmentType type = Punishment.PunishmentType.valueOf(ctx.request().getParam("type").toUpperCase());
User removedBy = User.findByIdSync(ctx.request().getParam("removedBy"));
if (removedBy == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("removedBy"));
return;
}
String reason = ctx.request().getParam("reason");
if (reason == null || reason.trim().isEmpty()) {
ErrorUtils.respondRequiredInput(ctx, "reason");
return;
}
for (Punishment punishment : Punishment.findByUserAndTypeSync(target, ImmutableSet.of(type))) {
if (punishment.isActive()) {
punishment.delete(removedBy, reason);
BlockingCallback<AuditLogEntry> blockingCallback = new BlockingCallback<>();
AuditLog.log(removedBy, "", ctx.get("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of(), blockingCallback);
blockingCallback.get();
APIv3.respondJson(ctx, punishment);
return;
}
}
ErrorUtils.respondGeneric(ctx, 404, "User provided has no active punishments");
}
}

View File

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

View File

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

View File

@ -0,0 +1,23 @@
package net.frozenorb.apiv3.route.punishments;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.Punishment;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class GETUserPunishments implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
APIv3.respondJson(ctx, Punishment.findByUserSync(target));
}
}

View File

@ -0,0 +1,93 @@
package net.frozenorb.apiv3.route.punishments;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.IpBan;
import net.frozenorb.apiv3.model.Punishment;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.unsorted.Permissions;
import net.frozenorb.apiv3.util.ErrorUtils;
import org.bson.Document;
import java.util.Date;
import java.util.Map;
public final class POSTUserPunish implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User target = User.findByIdSync(ctx.request().getParam("id"));
if (target == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
String reason = ctx.request().getParam("reason");
if (reason == null || reason.trim().isEmpty()) {
ErrorUtils.respondRequiredInput(ctx, "reason");
return;
}
Punishment.PunishmentType type = Punishment.PunishmentType.valueOf(ctx.request().getParam("type"));
if (type != Punishment.PunishmentType.WARN) {
for (Punishment punishment : Punishment.findByUserAndTypeSync(target, ImmutableSet.of(type))) {
if (punishment.isActive()) {
ErrorUtils.respondGeneric(ctx, 200, "A punishment by " + User.findByIdSync(punishment.getAddedBy()).getLastUsername() + " already covers this user.");
return;
}
}
}
Date expiresAt;
try {
expiresAt = new Date(Long.parseLong(ctx.request().getParam("expiresAt")));
} catch (NumberFormatException ex) {
expiresAt = null;
}
if (expiresAt != null && expiresAt.before(new Date())) {
ErrorUtils.respondInvalidInput(ctx, "Expiration date cannot be in the past.");
return;
}
Map<String, Object> meta = Document.parse(ctx.getBodyAsString());
if (meta == null) {
ErrorUtils.respondRequiredInput(ctx, "request body meta");
return;
}
// We purposely don't do a null check, punishments don't have to have a source.
User addedBy = User.findByIdSync(ctx.request().getParam("addedBy"));
if (target.hasPermissionAnywhere(Permissions.PROTECTED_PUNISHMENT)) {
ErrorUtils.respondGeneric(ctx, 200, target.getLastSeenOn() + " is protected from punishments.");
return;
}
Punishment punishment = new Punishment(target, reason, type, expiresAt, addedBy, ctx.get("actor"), meta);
String accessDenialReason = punishment.getAccessDenialReason();
String userIp = ctx.request().getParam("userIp");
if (userIp != null) {
IpBan ipBan = new IpBan(userIp, punishment);
ipBan.insert();
punishment.linkIpBan(ipBan);
}
punishment.insert();
APIv3.respondJson(ctx, ImmutableMap.of(
"punishment", punishment,
"accessDenialReason", accessDenialReason == null ? "" : accessDenialReason
));
}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,23 @@
package net.frozenorb.apiv3.route.ranks;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.Rank;
public final class POSTRank implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
String id = ctx.request().getParam("id");
int weight = Integer.parseInt(ctx.request().getParam("weight"));
String displayName = ctx.request().getParam("displayName");
String gameColor = ctx.request().getParam("gameColor");
String websiteColor = ctx.request().getParam("websiteColor");
boolean staffRank = Boolean.parseBoolean(ctx.request().getParam("staffRank"));
Rank rank = new Rank(id, weight, displayName, gameColor, websiteColor, staffRank);
rank.insert();
APIv3.respondJson(ctx, rank);
}
}

View File

@ -0,0 +1,4 @@
package net.frozenorb.apiv3.route.ranks;
public class PUTRank {
}

View File

@ -0,0 +1,23 @@
package net.frozenorb.apiv3.route.serverGroups;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.ServerGroup;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class DELETEServerGroup implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
ServerGroup serverGroup = ServerGroup.findById(ctx.request().getParam("id"));
if (serverGroup == null) {
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("id"));
return;
}
serverGroup.delete();
APIv3.respondJson(ctx, serverGroup);
}
}

View File

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

View File

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

View File

@ -0,0 +1,19 @@
package net.frozenorb.apiv3.route.serverGroups;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.ServerGroup;
public final class POSTServerGroup implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
String id = ctx.request().getParam("id");
String image = ctx.request().getParam("image");
ServerGroup serverGroup = new ServerGroup(id, image);
serverGroup.insert();
APIv3.respondJson(ctx, serverGroup);
}
}

View File

@ -0,0 +1,4 @@
package net.frozenorb.apiv3.route.serverGroups;
public class PUTServerGroup {
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,38 @@
package net.frozenorb.apiv3.route.servers;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.Server;
import net.frozenorb.apiv3.model.ServerGroup;
import net.frozenorb.apiv3.util.ErrorUtils;
import net.frozenorb.apiv3.util.IpUtils;
import java.math.BigInteger;
import java.util.Random;
public final class POSTServer implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
String id = ctx.request().getParam("id");
String displayName = ctx.request().getParam("displayName");
ServerGroup group = ServerGroup.findById(ctx.request().getParam("group"));
String ip = ctx.request().getParam("ip");
if (group == null) {
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("group"));
return;
}
if (!IpUtils.isValidIp(ip)) {
ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + ip + "\" is not valid.");
return;
}
String generatedApiKey = new BigInteger(130, new Random()).toString(32);
Server server = new Server(id, displayName, generatedApiKey, group, ip);
server.insert();
APIv3.respondJson(ctx, server);
}
}

View File

@ -0,0 +1,197 @@
package net.frozenorb.apiv3.route.servers;
import com.google.common.collect.ImmutableList;
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;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actor.Actor;
import net.frozenorb.apiv3.actor.ActorType;
import net.frozenorb.apiv3.model.*;
import net.frozenorb.apiv3.util.ErrorUtils;
import net.frozenorb.apiv3.util.PermissionUtils;
import net.frozenorb.apiv3.util.UUIDUtils;
import org.bson.Document;
import java.util.*;
@Slf4j
public final class POSTServerHeartbeat implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
Actor actor = ctx.get("actor");
if (actor.getType() != ActorType.SERVER) {
ErrorUtils.respondGeneric(ctx, 400, "This action can only be performed when requested by a server.");
return;
}
Server actorServer = Server.findById(actor.getName());
ServerGroup actorServerGroup = ServerGroup.findById(actorServer.getServerGroup());
Document reqJson = Document.parse(ctx.getBodyAsString());
Map<UUID, String> playerNames = extractPlayerNames(reqJson);
CompositeFuture.all(
createInfoResponse(actorServer, reqJson.getDouble("lastTps"), playerNames),
createPlayerResponse(actorServer, playerNames),
createPermissionsResponse(actorServerGroup),
createEventsResponse((List<Object>) reqJson.get("events"))
).setHandler((result) -> {
if (result.succeeded()) {
// We don't do anything with the info callback, as
// it's just to update our database.
APIv3.respondJson(ctx, ImmutableMap.of(
"players", result.result().result(1),
"permissions", result.result().result(2),
"events", result.result().result(3)
));
} else {
ErrorUtils.respondInternalError(ctx, result.cause());
}
});
}
// TODO: ASYNC (MAKE ALL SAVES/INSERTS/ETC USED HERE ASYNC
public Future<Void> createInfoResponse(Server server, double tps, Map<UUID, String> playerNames) {
Future<Void> callback = Future.future();
server.setPlayers(playerNames.keySet());
server.setLastTps(tps);
server.setLastUpdatedAt(new Date());
server.save();
callback.complete();
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();
}
// TODO: Provide IPs for ip ban lookup
response.put(uuid.toString(), user.createLoginInfo(server, punishments.get(uuid), ImmutableList.of(), 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);
}
callback.complete(permissionsResponse);
return callback;
}
public Future<Map<String, Object>> createEventsResponse(List<Object> eventsData) {
Future<Map<String, Object>> callback = Future.future();
for (Object event : eventsData) {
Document eventJson = (Document) event;
String type = eventJson.getString("type");
switch (type) {
case "join":
break;
case "leave":
break;
case "metrics":
break;
default:
log.warn("Recieved event with unknown type " + type + ".");
}
}
callback.complete(ImmutableMap.of());
return callback;
}
public Map<UUID, String> extractPlayerNames(Document reqJson) {
Map<UUID, String> result = new HashMap<>();
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);
}
}
return result;
}
}

View File

@ -0,0 +1,4 @@
package net.frozenorb.apiv3.route.servers;
public class PUTServer {
}

View File

@ -0,0 +1,39 @@
package net.frozenorb.apiv3.route.users;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.ServerGroup;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.model.UserMetaEntry;
import net.frozenorb.apiv3.util.ErrorUtils;
import org.bson.Document;
public final class DELETEUserMeta implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
ServerGroup serverGroup = ServerGroup.findById(ctx.request().getParam("serverGroup"));
if (serverGroup == null) {
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("serverGroup"));
return;
}
UserMetaEntry userMetaEntry = UserMetaEntry.findByUserAndGroupSync(user, serverGroup);
if (userMetaEntry != null) {
userMetaEntry.delete();
APIv3.respondJson(ctx, userMetaEntry.getData());
} else {
APIv3.respondJson(ctx, new Document());
}
}
}

View File

@ -1,36 +1,35 @@
package net.frozenorb.apiv3.routes.users;
package net.frozenorb.apiv3.route.users;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.Rank;
import net.frozenorb.apiv3.models.User;
import spark.Request;
import spark.Response;
import spark.Route;
import net.frozenorb.apiv3.model.Grant;
import net.frozenorb.apiv3.model.Rank;
import net.frozenorb.apiv3.model.User;
import java.util.*;
public final class GETStaff implements Route {
public final class GETStaff implements Handler<RoutingContext> {
public Object handle(Request req, Response res) {
public void handle(RoutingContext ctx) {
Map<String, Rank> staffRanks = new HashMap<>();
Rank.values().forEach(rank -> {
Rank.findAll().forEach(rank -> {
if (rank.isStaffRank()) {
staffRanks.put(rank.getId(), rank);
}
});
Map<String, Set<User>> result = new TreeMap<>((first, second) -> {
Rank firstRank = Rank.byId(first);
Rank secondRank = Rank.byId(second);
Rank firstRank = staffRanks.get(first);
Rank secondRank = staffRanks.get(second);
return Integer.compare(firstRank.getWeight(), secondRank.getWeight());
});
APIv3.getDatastore().createQuery(Grant.class).field("rank").in(staffRanks.keySet()).forEach(grant -> {
Grant.findByRankSync(staffRanks.values()).forEach(grant -> {
if (grant.isActive()) {
User user = User.byId(grant.getUser());
User user = User.findByIdSync(grant.getUser());
Rank rank = staffRanks.get(grant.getRank());
if (!result.containsKey(rank.getId())) {
@ -41,7 +40,7 @@ public final class GETStaff implements Route {
}
});
return result;
APIv3.respondJson(ctx, result);
}
}

View File

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

View File

@ -0,0 +1,35 @@
package net.frozenorb.apiv3.route.users;
import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.Grant;
import net.frozenorb.apiv3.model.IpLogEntry;
import net.frozenorb.apiv3.model.Punishment;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class GETUserDetails implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
// Too many fields to use .of()
APIv3.respondJson(ctx, ImmutableMap.builder()
.put("user", 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

@ -0,0 +1,32 @@
package net.frozenorb.apiv3.route.users;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.ServerGroup;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.model.UserMetaEntry;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class GETUserMeta implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
ServerGroup serverGroup = ServerGroup.findById(ctx.request().getParam("serverGroup"));
if (serverGroup == null) {
ErrorUtils.respondNotFound(ctx, "Server group", ctx.request().getParam("serverGroup"));
return;
}
UserMetaEntry userMetaEntry = UserMetaEntry.findByUserAndGroupSync(user, serverGroup);
APIv3.respondJson(ctx, userMetaEntry.getData());
}
}

View File

@ -0,0 +1,54 @@
package net.frozenorb.apiv3.route.users;
import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.unsorted.BlockingCallback;
import net.frozenorb.apiv3.util.ErrorUtils;
import net.frozenorb.apiv3.util.IpUtils;
import net.frozenorb.apiv3.util.TOTPUtils;
public final class GETUserRequiresTOTP implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
if (user.getTotpSecret() == null) {
APIv3.respondJson(ctx, ImmutableMap.of(
"required", false,
"message", "User does not have TOTP setup."
));
return;
}
String userIp = ctx.request().getParam("userIp");
if (!IpUtils.isValidIp(userIp)) {
ErrorUtils.respondInvalidInput(ctx, "Ip address \"" + userIp + "\" is not valid.");
return;
}
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"
));
} else {
APIv3.respondJson(ctx, ImmutableMap.of(
"required", true,
"message", "User has no TOTP exemptions."
));
}
}
}

View File

@ -0,0 +1,32 @@
package net.frozenorb.apiv3.route.users;
import com.google.common.collect.ImmutableMap;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.util.ErrorUtils;
public final class GETUserVerifyPassword implements Handler<RoutingContext> {
public void handle(RoutingContext ctx) {
User user = User.findByIdSync(ctx.request().getParam("id"));
if (user == null) {
ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id"));
return;
}
if (user.getPassword() == null) {
ErrorUtils.respondInvalidInput(ctx, "User provided does not have password set.");
return;
}
boolean authorized = user.checkPassword(ctx.request().getParam("password"));
APIv3.respondJson(ctx, ImmutableMap.of(
"authorized", authorized
));
}
}

Some files were not shown because too many files have changed in this diff Show More