Merged spark-experimental into master

This commit is contained in:
Colin McDonald 2016-05-05 16:02:23 -04:00
commit 27d139f0f6
124 changed files with 3236 additions and 1601 deletions

15
apiv3.properties Normal file
View File

@ -0,0 +1,15 @@
mongo.address=ds055505.mongolab.com
mongo.port=55505
mongo.database=minehqapi
mongo.username=test
mongo.password=test
redis.address=localhost
redis.port=6379
http.address=
http.port=80
http.workerThreads=6
twillio.accountSID=AC9e2f88c5690134d29a56f698de3cd740
twillio.authToken=982592505a171d3be6b0722f5ecacc0e
mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ
auth.permittedUserRanks=developer,owner
auth.websiteApiKey=RVbp4hY6sCFVaf

80
pom.xml
View File

@ -48,24 +48,84 @@
<dependencies>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-web</artifactId>
<version>3.2.0</version>
<groupId>com.sparkjava</groupId>
<artifactId>spark-core</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>19.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.6.2</version>
</dependency>
<dependency>
<groupId>com.librato.metrics</groupId>
<artifactId>metrics-librato</artifactId>
<version>4.1.2.5</version>
</dependency>
<dependency>
<groupId>io.dropwizard.metrics</groupId>
<artifactId>metrics-core</artifactId>
<version>3.1.2</version>
</dependency>
<dependency>
<groupId>com.bugsnag</groupId>
<artifactId>bugsnag</artifactId>
<version>2.0.0</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver-async</artifactId>
<version>3.0.4</version>
<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>
<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>org.projectlombok</groupId>

View File

@ -1,49 +1,190 @@
package net.frozenorb.apiv3;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress;
import com.mongodb.async.client.MongoDatabase;
import io.vertx.core.AbstractVerticle;
import io.vertx.ext.web.Router;
import lombok.Getter;
import net.frozenorb.apiv3.routes.*;
import net.frozenorb.apiv3.util.MongoUtils;
import net.frozenorb.apiv3.filters.ActorAttributeFilter;
import net.frozenorb.apiv3.filters.AuthorizationFilter;
import net.frozenorb.apiv3.filters.ContentTypeFilter;
import net.frozenorb.apiv3.models.NotificationTemplate;
import net.frozenorb.apiv3.routes.GETDump;
import net.frozenorb.apiv3.routes.GETWhoAmI;
import net.frozenorb.apiv3.routes.NotFound;
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.DELETEPunishment;
import net.frozenorb.apiv3.routes.punishments.GETPunishment;
import net.frozenorb.apiv3.routes.punishments.GETPunishments;
import net.frozenorb.apiv3.routes.punishments.POSTUserPunish;
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.FollowAnnotationExclusionStrategy;
import net.frozenorb.apiv3.serialization.ObjectIdTypeAdapter;
import net.frozenorb.apiv3.unsorted.LoggingExceptionHandler;
import net.frozenorb.apiv3.unsorted.Notification;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
import org.mongodb.morphia.logging.MorphiaLoggerFactory;
import org.mongodb.morphia.logging.slf4j.SLF4JLoggerImplFactory;
import redis.clients.jedis.JedisPool;
public final class APIv3 extends AbstractVerticle {
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;
@Getter private static MongoDatabase mongo;
import static spark.Spark.*;
// TODO: review the find* methods in models to make sure they're good (and sometimes not too broad, ex findAll on Users)
// TODO: consistency -- sometimes we refer to a user as user or target.
public final class APIv3 {
public void start() {
@Getter private static Datastore datastore;
@Getter private static Properties config = new Properties();
@Getter private static JedisPool redisPool;
@Getter private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(ObjectId.class, new ObjectIdTypeAdapter())
.setExclusionStrategies(new FollowAnnotationExclusionStrategy())
.create();
APIv3() {
//System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "trace");
setupConfig();
setupDatabase();
setupRedis();
setupHttp();
}
public void setupDatabase() {
mongo = MongoUtils.initializeConnection(ImmutableList.of(new ServerAddress("ds055505.mongolab.com", 55505)), "minehqapi", "test", "test".toCharArray());
private void setupConfig() {
try (InputStream in = new FileInputStream("apiv3.properties")) {
config.load(in);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public void setupHttp() {
Router rootRouter = Router.router(vertx);
private void setupDatabase() {
MongoClient mongoClient = new MongoClient(new ServerAddress(
config.getProperty("mongo.address"),
Integer.parseInt(config.getProperty("mongo.port"))),
ImmutableList.of(
MongoCredential.createCredential(
config.getProperty("mongo.username"),
config.getProperty("mongo.database"),
config.getProperty("mongo.password").toCharArray())
));
// We always reply in JSON.
rootRouter.route("/*").handler(ctx -> {
ctx.response().putHeader("content-type", "application/json");
ctx.next();
});
MorphiaLoggerFactory.reset();
MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class);
rootRouter.mountSubRouter("/auditLog", AuditLogRouter.create(vertx));
rootRouter.mountSubRouter("/grants", GrantsRouter.create(vertx));
rootRouter.mountSubRouter("/ipLog", IPLogRouter.create(vertx));
rootRouter.mountSubRouter("/notifications", NotificationsRouter.create(vertx));
rootRouter.mountSubRouter("/punishments", PunishmentsRouter.create(vertx));
rootRouter.mountSubRouter("/serverGroups", ServerGroupsRouter.create(vertx));
rootRouter.mountSubRouter("/servers", ServersRouter.create(vertx));
rootRouter.mountSubRouter("/users", UsersRouter.create(vertx));
Morphia morphia = new Morphia();
morphia.mapPackage("net.frozenorb.apiv3.accessor");
vertx.createHttpServer().requestHandler(rootRouter::accept).listen(8080);
datastore = morphia.createDatastore(mongoClient, config.getProperty("mongo.database"));
datastore.ensureIndexes();
}
private void setupRedis() {
redisPool = new JedisPool(
config.getProperty("redis.address"),
Integer.parseInt(config.getProperty("redis.port"))
);
}
private void setupHttp() {
ipAddress(config.getProperty("http.address"));
port(Integer.parseInt(config.getProperty("http.port")));
threadPool(Integer.parseInt(config.getProperty("http.workerThreads")));
before(new ContentTypeFilter());
before(new ActorAttributeFilter());
before(new AuthorizationFilter());
exception(Exception.class, new LoggingExceptionHandler());
// TODO: The commented out routes
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);
get("/grant/:id", new GETGrant(), gson::toJson);
get("/grants", new GETGrants(), gson::toJson);
delete("/grant/:id", new DELETEGrant(), gson::toJson);
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);
get("/punishment/:id", new GETPunishment(), gson::toJson);
get("/punishments", new GETPunishments(), gson::toJson);
delete("/punishment/:id", new DELETEPunishment(), gson::toJson);
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);
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);
get("/server/:id", new GETServer(), gson::toJson);
get("/servers", new GETServers(), gson::toJson);
post("/server/heartbeat", new POSTServerHeartbeat(), gson::toJson);
post("/server/metrics", new POSTServerMetrics(), gson::toJson);
post("/server", new POSTServer(), gson::toJson);
//put("/server/:id", new PUTServer(), gson::toJson);
delete("/server/:id", new DELETEServer(), gson::toJson);
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/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/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);
// 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);
}
}

View File

@ -1,12 +0,0 @@
package net.frozenorb.apiv3;
import org.bson.Document;
public interface LiteFullJson {
Document toLiteJson();
default Document toFullJson() {
return toLiteJson();
}
}

View File

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

View File

@ -1,54 +0,0 @@
package net.frozenorb.apiv3.accessor;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.AuditLogEntry;
import net.frozenorb.apiv3.util.MongoUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.util.Collection;
import java.util.Date;
import java.util.UUID;
@UtilityClass
public class AuditLog {
public static final String COLLECTION_NAME = "auditLog";
private static final Document TIME_BASED_SORT = new Document("performedAt", -1);
public static void findAll(SingleResultCallback<Collection<AuditLogEntry>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document(), TIME_BASED_SORT, AuditLogEntry::new, callback);
}
public static void findByPerformer(UUID performer, SingleResultCallback<Collection<AuditLogEntry>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("performedBy", performer.toString()), TIME_BASED_SORT, AuditLogEntry::new, callback);
}
public static void findByPerformerIp(String performerIp, SingleResultCallback<Collection<AuditLogEntry>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("performedFrom", performerIp), TIME_BASED_SORT, AuditLogEntry::new, callback);
}
public static void findByActionType(String actionType, SingleResultCallback<Collection<AuditLogEntry>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("actionType", actionType), TIME_BASED_SORT, AuditLogEntry::new, callback);
}
public static void log(UUID user, String userIp, String actionType, SingleResultCallback<Void> callback) {
log(user, userIp, actionType, callback);
}
public static void log(UUID user, String userIp, String actionType, Document data, SingleResultCallback<Void> callback) {
Document insert = new Document();
insert.put("_id", new ObjectId().toString());
insert.put("performedBy", user.toString());
insert.put("performedAt", new Date());
insert.put("performedFrom", userIp);
insert.put("actionType", actionType);
insert.put("actionData", data);
APIv3.getMongo().getCollection(COLLECTION_NAME).insertOne(insert, callback);
}
}

View File

@ -1,50 +0,0 @@
package net.frozenorb.apiv3.accessor;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.Grant;
import net.frozenorb.apiv3.util.MongoUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.time.Instant;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
@UtilityClass
public class Grants {
public static final String COLLECTION_NAME = "grant";
private static final Document TIME_BASED_SORT = new Document("addedAt", -1);
public static void findById(String id, SingleResultCallback<Grant> callback) {
MongoUtils.findOneAndTransform(COLLECTION_NAME, new Document("_id", id), Grant::new, callback);
}
public static void findByTarget(UUID target, SingleResultCallback<Collection<Grant>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("target", target.toString()), TIME_BASED_SORT, Grant::new, callback);
}
public static void findByAddedBy(UUID addedBy, SingleResultCallback<Collection<Grant>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("addedBy", addedBy.toString()), TIME_BASED_SORT, Grant::new, callback);
}
public static void createGrant(UUID target, Set<String> scopes, String rank, Instant expiresAt, UUID addedBy, String reason, SingleResultCallback<Void> callback) {
Document insert = new Document();
insert.put("_id", new ObjectId().toString());
insert.put("target", target.toString());
insert.put("reason", reason);
insert.put("scopes", scopes);
insert.put("rank", rank);
insert.put("expiresAt", expiresAt);
insert.put("addedBy", addedBy.toString());
insert.put("addedAt", Instant.now());
APIv3.getMongo().getCollection(COLLECTION_NAME).insertOne(insert, callback);
}
}

View File

@ -1,24 +0,0 @@
package net.frozenorb.apiv3.accessor;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.model.IPBan;
import net.frozenorb.apiv3.util.MongoUtils;
import org.bson.Document;
import java.util.Collection;
@UtilityClass
public class IPBans {
public static final String COLLECTION_NAME = "ipBan";
public static void findAll(SingleResultCallback<Collection<IPBan>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document(), IPBan::new, callback);
}
public static void findById(String id, SingleResultCallback<IPBan> callback) {
MongoUtils.findOneAndTransform(COLLECTION_NAME, new Document("_id", id), IPBan::new, callback);
}
}

View File

@ -1,52 +0,0 @@
package net.frozenorb.apiv3.accessor;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.IPLogEntry;
import net.frozenorb.apiv3.util.MongoUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.time.Instant;
import java.util.Collection;
import java.util.UUID;
@UtilityClass
public class IPLog {
public static final String COLLECTION_NAME = "ipLog";
private static final Document TIME_BASED_SORT = new Document("lastSeen", -1);
public static void findByUser(UUID user, SingleResultCallback<Collection<IPLogEntry>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("user", user.toString()), TIME_BASED_SORT, IPLogEntry::new, callback);
}
public static void findByIp(String ip, SingleResultCallback<Collection<IPLogEntry>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("ip", ip), TIME_BASED_SORT, IPLogEntry::new, callback);
}
public static void log(UUID user, String ip , SingleResultCallback<Void> callback) {
MongoUtils.findOneAndTransform(COLLECTION_NAME, new Document("user", user.toString()).append("ip", ip), IPLogEntry::new, (ipLogEntry, error) -> {
if (error != null) {
callback.onResult(null, error);
} else if (ipLogEntry != null) {
ipLogEntry.used(callback);
} else {
Document insert = new Document();
insert.put("_id", new ObjectId().toString());
insert.put("user", user.toString());
insert.put("ip", ip);
insert.put("lastSeen", Instant.now());
insert.put("firstSeen", Instant.now());
insert.put("uses", 1);
APIv3.getMongo().getCollection(COLLECTION_NAME).insertOne(insert, (ignored, error2) -> {
callback.onResult(null, error2);
});
}
});
}
}

View File

@ -1,45 +0,0 @@
package net.frozenorb.apiv3.accessor;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.NotificationLogEntry;
import net.frozenorb.apiv3.util.MongoUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.time.Instant;
import java.util.Collection;
import java.util.UUID;
@UtilityClass
public class NotificationLog {
public static final String COLLECTION_NAME = "notificationLog";
public static void findByTarget(UUID target, SingleResultCallback<Collection<NotificationLogEntry>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("target", target.toString()), NotificationLogEntry::new, callback);
}
public static void logEmail(UUID user, String title, String body, SingleResultCallback<Void> callback) {
log(user, NotificationLogEntry.NotificationType.EMAIL, title, body, callback);
}
public static void logSMS(UUID user, String title, String body, SingleResultCallback<Void> callback) {
log(user, NotificationLogEntry.NotificationType.SMS, title, body, callback);
}
private static void log(UUID user, NotificationLogEntry.NotificationType type, String title, String body, SingleResultCallback<Void> callback) {
Document insert = new Document();
insert.put("_id", new ObjectId().toString());
insert.put("target", user.toString());
insert.put("sentAt", Instant.now());
insert.put("type", type.name());
insert.put("title", title);
insert.put("body", body);
APIv3.getMongo().getCollection(COLLECTION_NAME).insertOne(insert, callback);
}
}

View File

@ -1,39 +0,0 @@
package net.frozenorb.apiv3.accessor;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.NotificationTemplate;
import net.frozenorb.apiv3.util.MongoUtils;
import org.bson.Document;
import java.time.Instant;
import java.util.Collection;
import java.util.UUID;
@UtilityClass
public class NotificationTemplates {
public static final String COLLECTION_NAME = "notificationTemplate";
public static void findAll(SingleResultCallback<Collection<NotificationTemplate>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document(), NotificationTemplate::new, callback);
}
public static void findById(String id, SingleResultCallback<NotificationTemplate> callback) {
MongoUtils.findOneAndTransform(COLLECTION_NAME, new Document("_id", id), NotificationTemplate::new, callback);
}
public static void createNotificationTemplate(String id, String title, String body, UUID creator, SingleResultCallback<Void> callback) {
Document insert = new Document();
insert.put("_id", id);
insert.put("title", title);
insert.put("body", body);
insert.put("lastUpdatedAt", Instant.now());
insert.put("lastUpdatedBy", creator.toString());
APIv3.getMongo().getCollection(COLLECTION_NAME).insertOne(insert, callback);
}
}

View File

@ -1,50 +0,0 @@
package net.frozenorb.apiv3.accessor;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.model.Grant;
import net.frozenorb.apiv3.model.Punishment;
import net.frozenorb.apiv3.util.MongoUtils;
import org.bson.Document;
import org.bson.types.ObjectId;
import java.time.Instant;
import java.util.Collection;
import java.util.UUID;
@UtilityClass
public class Punishments {
public static final String COLLECTION_NAME = "punishment";
private static final Document TIME_BASED_SORT = new Document("addedAt", -1);
public static void findById(String id, SingleResultCallback<Punishment> callback) {
MongoUtils.findOneAndTransform(COLLECTION_NAME, new Document("_id", id), Punishment::new, callback);
}
public static void findByTarget(UUID target, SingleResultCallback<Collection<Punishment>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("target", target.toString()), TIME_BASED_SORT, Punishment::new, callback);
}
public static void findByAddedBy(UUID addedBy, SingleResultCallback<Collection<Grant>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("addedBy", addedBy.toString()), TIME_BASED_SORT, Grant::new, callback);
}
public static void createPunishment(UUID target, Punishment.PunishmentType type, Instant expiresAt, UUID addedBy, String addedOn, String reason, SingleResultCallback<Void> callback) {
Document insert = new Document();
insert.put("_id", new ObjectId().toString());
insert.put("target", target.toString());
insert.put("reason", reason);
insert.put("type", type.name());
insert.put("expiresAt", expiresAt);
insert.put("addedBy", addedBy.toString());
insert.put("addedAt", Instant.now());
insert.put("addedOn", addedOn);
APIv3.getMongo().getCollection(COLLECTION_NAME).insertOne(insert, callback);
}
}

View File

@ -1,24 +0,0 @@
package net.frozenorb.apiv3.accessor;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.model.ServerGroup;
import net.frozenorb.apiv3.util.MongoUtils;
import org.bson.Document;
import java.util.Collection;
@UtilityClass
public class ServerGroups {
public static final String COLLECTION_NAME = "serverGroup";
public static void findAll(SingleResultCallback<Collection<ServerGroup>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document(), ServerGroup::new, callback);
}
public static void findById(String id, SingleResultCallback<ServerGroup> callback) {
MongoUtils.findOneAndTransform(COLLECTION_NAME, new Document("_id", id), ServerGroup::new, callback);
}
}

View File

@ -1,28 +0,0 @@
package net.frozenorb.apiv3.accessor;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.model.Server;
import net.frozenorb.apiv3.util.MongoUtils;
import org.bson.Document;
import java.util.Collection;
@UtilityClass
public class Servers {
public static final String COLLECTION_NAME = "server";
public static void findAll(SingleResultCallback<Collection<Server>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document(), Server::new, callback);
}
public static void findById(String id, SingleResultCallback<Server> callback) {
MongoUtils.findOneAndTransform(COLLECTION_NAME, new Document("_id", id), Server::new, callback);
}
public static void findByGroup(String groupId, SingleResultCallback<Collection<Server>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document("group", groupId), Server::new, callback);
}
}

View File

@ -1,25 +0,0 @@
package net.frozenorb.apiv3.accessor;
import com.mongodb.async.SingleResultCallback;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.model.User;
import net.frozenorb.apiv3.util.MongoUtils;
import org.bson.Document;
import java.util.Collection;
import java.util.UUID;
@UtilityClass
public class Users {
public static final String COLLECTION_NAME = "user";
public static void findAll(SingleResultCallback<Collection<User>> callback) {
MongoUtils.findAndTransform(COLLECTION_NAME, new Document(), User::new, callback);
}
public static void findById(UUID id, SingleResultCallback<User> callback) {
MongoUtils.findOneAndTransform(COLLECTION_NAME, new Document("_id", id.toString()), User::new, callback);
}
}

View File

@ -0,0 +1,9 @@
package net.frozenorb.apiv3.actors;
public interface Actor {
boolean isAuthorized();
String getName();
ActorType getType();
}

View File

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

View File

@ -0,0 +1,25 @@
package net.frozenorb.apiv3.actors;
import net.frozenorb.apiv3.models.Server;
public final class ServerActor implements Actor {
private final Server server;
public ServerActor(Server server) {
this.server = server;
}
public boolean isAuthorized() {
return true;
}
public String getName() {
return server.getId();
}
public ActorType getType() {
return ActorType.SERVER;
}
}

View File

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

View File

@ -0,0 +1,39 @@
package net.frozenorb.apiv3.actors;
import com.google.common.collect.ImmutableSet;
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> authorizedUserGrants = ImmutableSet.copyOf(APIv3.getConfig().getProperty("auth.permittedUserRanks").split(","));
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 = authorizedUserGrants.contains(highestRankId);
return cachedAuthorized;
}
}
public String getName() {
return user.getLastUsername();
}
public ActorType getType() {
return ActorType.USER;
}
}

View File

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

View File

@ -0,0 +1,24 @@
package net.frozenorb.apiv3.auditLog;
import com.google.common.collect.ImmutableMap;
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 org.bson.Document;
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, Map<String, Object> actionData) {
APIv3.getDatastore().save(new AuditLogEntry(performedBy, performedByIp, actor, actionType, actionData));
}
}

View File

@ -0,0 +1,26 @@
package net.frozenorb.apiv3.auditLog;
import net.frozenorb.apiv3.models.AuditLogEntry;
public enum AuditLogActionType {
DELETE_PUNISHMENT {
@Override
public void revert(AuditLogEntry entry) {
}
},
DELETE_GRANT {
@Override
public void revert(AuditLogEntry entry) {
}
};
public void revert(AuditLogEntry entry) {}
}

View File

@ -0,0 +1,80 @@
package net.frozenorb.apiv3.filters;
import com.sun.xml.internal.messaging.saaj.util.Base64;
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;
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));
} 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) {
String encodedHeader = authHeader.substring("Basic ".length());
String[] credentials = Base64.base64Decode(encodedHeader).split(":");
if (credentials.length == 2) {
User user = User.byLastUsername(credentials[0]);
char[] password = credentials[1].toCharArray();
if (user != null && user.checkPassword(password)) {
return new UserActor(user);
}
}
Spark.halt(401, APIv3.getGson().toJson(ErrorUtils.error("Failed to authorize.")));
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);
}
}
}
Spark.halt(401, APIv3.getGson().toJson(ErrorUtils.error("Failed to authorize.")));
return null;
}
}

View File

@ -0,0 +1,21 @@
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()) {
Spark.halt(APIv3.getGson().toJson(ErrorUtils.error("Unauthorized access: Please authenticate as either a server, the website, or an authorized user. You're currently authorized as " + actor.getName())));
}
}
}

View File

@ -0,0 +1,13 @@
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.header("content-type", "application/json");
}
}

View File

@ -1,47 +0,0 @@
package net.frozenorb.apiv3.model;
import lombok.Getter;
import lombok.ToString;
import net.frozenorb.apiv3.accessor.AuditLog;
import org.bson.Document;
import java.time.Instant;
import java.util.UUID;
@ToString
public final class AuditLogEntry extends BaseModel {
@Getter private String id;
@Getter private UUID performedBy;
@Getter private Instant performedAt;
@Getter private String performedFrom;
@Getter private String actionType;
@Getter private Document actionData;
public AuditLogEntry(Document json) {
super(AuditLog.COLLECTION_NAME);
this.id = json.getString("_id");
this.performedBy = UUID.fromString(json.getString("performedBy"));
this.performedAt = (Instant) json.get("performedAt");
this.performedFrom = json.getString("performedFrom");
this.actionType = json.getString("actionType");
this.actionData = (Document) json.get("actionData");
setId(id);
}
public Document toLiteJson() {
Document json = new Document();
json.put("id", id);
json.put("performedBy", performedBy.toString());
json.put("performedAt", performedAt.toString());
json.put("performedFrom", performedFrom);
json.put("actionType", actionType);
json.put("actionData", actionData);
return json;
}
}

View File

@ -1,30 +0,0 @@
package net.frozenorb.apiv3.model;
import com.mongodb.async.SingleResultCallback;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.LiteFullJson;
import org.bson.Document;
public abstract class BaseModel implements LiteFullJson {
private final String collectionName;
@Setter private String id;
public BaseModel(String collectionName) {
this.collectionName = collectionName;
}
protected void update(Document update, SingleResultCallback<Void> callback) {
APIv3.getMongo().getCollection(collectionName).updateOne(new Document("_id", id), update, (updateResult, error) -> {
callback.onResult(null, error);
});
}
protected void delete(SingleResultCallback<Void> callback) {
APIv3.getMongo().getCollection(collectionName).deleteOne(new Document("_id", id), (deleteResult, error) -> {
callback.onResult(null, error);
});
}
}

View File

@ -1,114 +0,0 @@
package net.frozenorb.apiv3.model;
import com.google.common.collect.ImmutableSet;
import com.mongodb.async.SingleResultCallback;
import lombok.Getter;
import lombok.ToString;
import net.frozenorb.apiv3.accessor.Grants;
import org.bson.Document;
import java.time.Instant;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
@ToString
public final class Grant extends BaseModel {
@Getter private String id;
@Getter private UUID target;
@Getter private String reason;
@Getter private Set<String> scopes;
@Getter private String rank;
@Getter private Instant expiresAt;
@Getter private UUID addedBy;
@Getter private Instant addedAt;
@Getter private UUID removedBy;
@Getter private Instant removedAt;
@Getter private String removalReason;
public Grant(Document json) {
super(Grants.COLLECTION_NAME);
this.id = json.getString("_id");
this.target = UUID.fromString(json.getString("target"));
this.reason = json.getString("reason");
this.scopes = ImmutableSet.copyOf((Collection) json.get("scopes")); // This is a safe cast, the collection's type is always String
this.rank = json.getString("rank");
this.expiresAt = (Instant) json.get("expiresAt");
this.addedBy = UUID.fromString(json.getString("addedBy"));
this.addedAt = (Instant) json.get("addedAt");
if (json.containsKey("removedBy")) {
this.removedBy = UUID.fromString(json.getString("removedBy"));
this.removedAt = (Instant) json.get("removedAt");
this.removalReason = json.getString("removalReason");
}
setId(id);
}
public void delete(UUID removedBy, String reason, SingleResultCallback<Void> callback) {
this.removedBy = removedBy;
this.removedAt = Instant.now();
this.removalReason = reason;
Document set = new Document();
set.put("removedBy", removedBy.toString());
set.put("removedAt", removedAt);
set.put("removalReason", removalReason);
super.update(new Document("$set", set), callback);
}
public boolean isActive() {
return !(isExpired() || isRemoved());
}
public boolean isExpired() {
if (expiresAt == null) {
return false; // Never expires
} else {
return expiresAt.isAfter(Instant.now());
}
}
public boolean isRemoved() {
return removedBy != null;
}
public Document toLiteJson() {
Document json = new Document();
json.put("id", id);
json.put("target", target.toString());
json.put("reason", reason);
json.put("scopes", scopes);
json.put("rank", rank);
json.put("expiresAt", expiresAt == null ? null : expiresAt.toString());
json.put("addedBy", addedBy.toString());
json.put("addedAt", addedAt.toString());
Document statusJson = new Document();
statusJson.put("active", isActive());
statusJson.put("expired", isExpired());
statusJson.put("removed", isRemoved());
json.put("status", statusJson);
if (removedBy != null) {
json.put("removedBy", removedBy.toString());
json.put("removedAt", removedAt.toString());
json.put("removalReason", removalReason);
}
return json;
}
}

View File

@ -1,37 +0,0 @@
package net.frozenorb.apiv3.model;
import lombok.Getter;
import lombok.ToString;
import net.frozenorb.apiv3.accessor.IPBans;
import org.bson.Document;
@ToString
public final class IPBan extends BaseModel {
@Getter private String id;
public IPBan(Document json) {
super(IPBans.COLLECTION_NAME);
this.id = json.getString("_id");
setId(id);
}
public Document toLiteJson() {
Document json = new Document();
json.put("_id", id);
return json;
}
public Document toFullJson() {
Document json = toLiteJson();
return json;
}
}

View File

@ -1,62 +0,0 @@
package net.frozenorb.apiv3.model;
import com.mongodb.async.SingleResultCallback;
import lombok.Getter;
import lombok.ToString;
import net.frozenorb.apiv3.accessor.IPLog;
import org.bson.Document;
import java.time.Instant;
import java.util.UUID;
@ToString
public final class IPLogEntry extends BaseModel {
@Getter private String id;
@Getter private UUID user;
@Getter private String ip;
@Getter private Instant firstSeen;
@Getter private Instant lastSeen;
@Getter private int uses;
public IPLogEntry(Document json) {
super(IPLog.COLLECTION_NAME);
this.id = json.getString("_id");
this.user = UUID.fromString(json.getString("user"));
this.ip = json.getString("ip");
this.lastSeen = (Instant) json.get("lastSeen");
this.firstSeen = (Instant) json.get("firstSeen");
this.uses = json.getInteger("uses");
setId(id);
}
public void used(SingleResultCallback<Void> callback) {
this.lastSeen = Instant.now();
this.uses++;
Document set = new Document();
set.put("lastSeen", lastSeen);
set.put("uses", uses);
super.update(new Document("$set", set), (updateResult, error) -> {
callback.onResult(null, error);
});
}
public Document toLiteJson() {
Document json = new Document();
json.put("id", id);
json.put("user", user.toString());
json.put("ip", ip);
json.put("firstSeen", firstSeen.toString());
json.put("lastSeen", lastSeen.toString());
json.put("uses", uses);
return json;
}
}

View File

@ -1,53 +0,0 @@
package net.frozenorb.apiv3.model;
import lombok.Getter;
import lombok.ToString;
import net.frozenorb.apiv3.accessor.NotificationLog;
import org.bson.Document;
import java.time.Instant;
import java.util.UUID;
@ToString
public final class NotificationLogEntry extends BaseModel {
@Getter private String id;
@Getter private UUID target;
@Getter private Instant sentAt;
@Getter private NotificationType type;
@Getter private String title;
@Getter private String body;
public NotificationLogEntry(Document json) {
super(NotificationLog.COLLECTION_NAME);
this.id = json.getString("_id");
this.target = UUID.fromString(json.getString("target"));
this.sentAt = (Instant) json.get("sentAt");
this.type = NotificationType.valueOf(json.getString("type"));
this.title = json.getString("title");
this.body = json.getString("body");
setId(id);
}
public Document toLiteJson() {
Document json = new Document();
json.put("id", id);
json.put("target", target.toString());
json.put("sentAt", sentAt.toString());
json.put("type", type.name());
json.put("title", title);
json.put("body", body);
return json;
}
public enum NotificationType {
EMAIL, SMS
}
}

View File

@ -1,87 +0,0 @@
package net.frozenorb.apiv3.model;
import com.mongodb.async.SingleResultCallback;
import lombok.Getter;
import lombok.ToString;
import net.frozenorb.apiv3.accessor.NotificationTemplates;
import org.bson.Document;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
@ToString
public final class NotificationTemplate extends BaseModel {
@Getter private String id;
@Getter private String title;
@Getter private String body;
@Getter private Instant lastUpdatedAt;
@Getter private UUID lastUpdatedBy;
public NotificationTemplate(Document json) {
super(NotificationTemplates.COLLECTION_NAME);
this.id = json.getString("_id");
this.title = json.getString("title");
this.body = json.getString("body");
this.lastUpdatedAt = (Instant) json.get("lastUpdatedAt");
this.lastUpdatedBy = UUID.fromString(json.getString("lastUpdatedBy"));
setId(id);
}
public Document toLiteJson() {
Document json = new Document();
json.put("id", id);
json.put("title", title);
json.put("body", body);
json.put("lastUpdatedAt", lastUpdatedAt.toString());
json.put("lastUpdatedBy", lastUpdatedBy.toString());
return json;
}
public void update(String title, String body, UUID updatedBy, SingleResultCallback<Void> callback) {
this.title = title;
this.body = body;
this.lastUpdatedAt = Instant.now();
this.lastUpdatedBy = updatedBy;
Document set = new Document();
set.put("title", title);
set.put("body", body);
set.put("lastUpdatedAt", lastUpdatedAt.toString());
set.put("lastUpdatedBy", lastUpdatedBy.toString());
super.update(new Document("$set", set), (updateResult, error) -> {
callback.onResult(null, error);
});
}
public void delete(SingleResultCallback<Void> callback) {
super.delete(callback);
}
public String fillTitle(Map<String, Object> replacements) {
return fill(title, 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,116 +0,0 @@
package net.frozenorb.apiv3.model;
import com.mongodb.async.SingleResultCallback;
import lombok.Getter;
import lombok.ToString;
import net.frozenorb.apiv3.accessor.Punishments;
import org.bson.Document;
import java.time.Instant;
import java.util.UUID;
@ToString
public final class Punishment extends BaseModel {
@Getter private String id;
@Getter private UUID target;
@Getter private String reason;
@Getter private PunishmentType type;
@Getter private Instant expiresAt;
@Getter private UUID addedBy;
@Getter private Instant addedAt;
@Getter private String addedOn;
@Getter private UUID removedBy;
@Getter private Instant removedAt;
@Getter private String removalReason;
public Punishment(Document json) {
super(Punishments.COLLECTION_NAME);
this.id = json.getString("_id");
this.target = UUID.fromString(json.getString("target"));
this.reason = json.getString("reason");
this.type = PunishmentType.valueOf(json.getString("type"));
this.expiresAt = (Instant) json.get("expiresAt");
this.addedBy = UUID.fromString(json.getString("addedBy"));
this.addedAt = (Instant) json.get("addedAt");
this.addedOn = json.getString("addedOn");
if (json.containsKey("removedBy")) {
this.removedBy = UUID.fromString(json.getString("removedBy"));
this.removedAt = (Instant) json.get("removedAt");
this.removalReason = json.getString("removalReason");
}
setId(id);
}
public void delete(UUID removedBy, String reason, SingleResultCallback<Void> callback) {
this.removedBy = removedBy;
this.removedAt = Instant.now();
this.removalReason = reason;
Document set = new Document();
set.put("removedBy", removedBy.toString());
set.put("removedAt", removedAt);
set.put("removalReason", removalReason);
super.update(new Document("$set", set), callback);
}
public boolean isActive() {
return !(isExpired() || isRemoved());
}
public boolean isExpired() {
if (expiresAt == null) {
return false; // Never expires
} else {
return expiresAt.isAfter(Instant.now());
}
}
public boolean isRemoved() {
return removedBy != null;
}
public Document toLiteJson() {
Document json = new Document();
json.put("id", id);
json.put("target", target.toString());
json.put("reason", reason);
json.put("type", type.name());
json.put("expiresAt", expiresAt == null ? null : expiresAt.toString());
json.put("addedBy", addedBy.toString());
json.put("addedAt", addedAt.toString());
Document statusJson = new Document();
statusJson.put("active", isActive());
statusJson.put("expired", isExpired());
statusJson.put("removed", isRemoved());
json.put("status", statusJson);
if (removedBy != null) {
json.put("removedBy", removedBy.toString());
json.put("removedAt", removedAt.toString());
json.put("removalReason", removalReason);
}
return json;
}
public enum PunishmentType {
BAN, MUTE, WARN
}
}

View File

@ -1,70 +0,0 @@
package net.frozenorb.apiv3.model;
import lombok.Getter;
import lombok.ToString;
import net.frozenorb.apiv3.accessor.Servers;
import org.bson.Document;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@ToString
public final class Server extends BaseModel {
@Getter private String id;
@Getter private String bungeeId;
@Getter private String displayName;
@Getter private String secret;
@Getter private String group;
@Getter private String ip;
@Getter private Instant lastUpdate;
@Getter private double lastTps;
@Getter private List<UUID> players;
public Server(Document json) {
super(Servers.COLLECTION_NAME);
this.id = json.getString("_id");
this.bungeeId = json.getString("bungeeId");
this.displayName = json.getString("displayName");
this.secret = json.getString("secret");
this.group = json.getString("group");
this.ip = json.getString("ip");
this.lastUpdate = (Instant) json.get("lastUpdate");
this.lastTps = ((Number) json.get("lastTps")).doubleValue();
this.players = new ArrayList<>();
for (Object uuidString : (Collection) json.get("players")) {
players.add(UUID.fromString((String) uuidString));
}
setId(id);
}
public Document toLiteJson() {
Document json = new Document();
json.put("id", id);
json.put("bungeeId", bungeeId);
json.put("displayName", displayName);
json.put("group", group);
json.put("ip", ip);
return json;
}
public Document toFullJson() {
Document json = toLiteJson();
json.put("lastUpdate", lastUpdate.toString());
json.put("lastTps", lastTps);
json.put("players", players.stream().map(UUID::toString).collect(Collectors.toList()));
return json;
}
}

View File

@ -1,37 +0,0 @@
package net.frozenorb.apiv3.model;
import lombok.Getter;
import lombok.ToString;
import net.frozenorb.apiv3.accessor.ServerGroups;
import org.bson.Document;
@ToString
public final class ServerGroup extends BaseModel {
@Getter private String id;
public ServerGroup(Document json) {
super(ServerGroups.COLLECTION_NAME);
this.id = json.getString("_id");
setId(id);
}
public Document toLiteJson() {
Document json = new Document();
json.put("_id", id);
return json;
}
public Document toFullJson() {
Document json = toLiteJson();
return json;
}
}

View File

@ -1,67 +0,0 @@
package net.frozenorb.apiv3.model;
import lombok.Getter;
import lombok.ToString;
import net.frozenorb.apiv3.accessor.Users;
import org.bson.Document;
import java.time.Instant;
import java.util.Map;
import java.util.UUID;
@ToString
public final class User extends BaseModel {
@Getter private UUID id;
@Getter private String lastName;
@Getter private Map<String, Instant> aliases;
@Getter private String otpCode;
@Getter private String password;
@Getter private String passwordSalt;
@Getter private String email;
@Getter private int phoneNumber;
@Getter private String lastSeenOn;
@Getter private Instant lastSeenAt;
@Getter private Instant firstSeen;
public User(Document json) {
super(Users.COLLECTION_NAME);
this.id = UUID.fromString(json.getString("_id"));
this.lastName = json.getString("lastName");
this.aliases = (Map<String, Instant>) json.get("aliases");
this.otpCode = json.getString("otpCode");
this.password = json.getString("password");
this.passwordSalt = json.getString("passwordSalt");
this.email = json.getString("email");
this.phoneNumber = json.getInteger("phoneNumber");
this.lastSeenOn = json.getString("lastSeenOn");
this.lastSeenAt = (Instant) json.get("lastSeenAt");
this.firstSeen = (Instant) json.get("firstSeen");
setId(id.toString());
}
public Document toLiteJson() {
Document json = new Document();
json.put("id", id.toString());
json.put("lastName", lastName);
json.put("email", email);
json.put("phoneNumber", phoneNumber);
json.put("lastSeenOn", lastSeenOn);
json.put("lastSeenAt", lastSeenAt.toString());
json.put("firstSeen", firstSeen.toString());
return json;
}
public Document toFullJson() {
Document json = toLiteJson();
json.put("aliases", aliases);
return json;
}
}

View File

@ -0,0 +1,42 @@
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 performedBy;
@Getter private String performedByIp;
@Getter @Indexed private Date performedAt;
@Getter private String actorName;
@Getter private ActorType actorType;
@Getter private AuditLogActionType actionType;
@Getter private Map<String, Object> actionData;
public AuditLogEntry() {} // For Morphia
public AuditLogEntry(User performedBy, String performedByIp, Actor actor, AuditLogActionType actionType, Map<String, Object> actionData) {
this.id = new ObjectId().toString();
this.performedBy = performedBy.getId();
this.performedByIp = performedByIp;
this.performedAt = new Date();
this.actorName = actor.getName();
this.actorType = actor.getType();
this.actionType = actionType;
this.actionData = ImmutableMap.copyOf(actionData);
}
}

View File

@ -0,0 +1,84 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.Collections2;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
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.HashSet;
import java.util.Set;
import java.util.UUID;
@Entity(value = "grants", noClassnameStored = true)
public final class Grant {
@Getter @Id private String id;
@Getter @Indexed private UUID target;
@Getter private String reason;
@Getter private Set<String> scopes;
@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 Grant() {} // For Morphia
public Grant(User target, String reason, Set<ServerGroup> scopes, Rank rank, Date expiresAt, User addedBy) {
this.id = new ObjectId().toString();
this.target = target.getId();
this.reason = reason;
this.scopes = new HashSet<>(Collections2.transform(scopes, ServerGroup::getId));
this.rank = rank.getId();
this.expiresAt = (Date) expiresAt.clone();
this.addedBy = 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.after(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

@ -0,0 +1,42 @@
package net.frozenorb.apiv3.models;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
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.UUID;
@Entity(value = "ipLog", noClassnameStored = true)
public final class IPLogEntry {
@Getter @Id private String id;
@Getter @ExcludeFromReplies @Indexed private UUID user;
@Getter @Indexed private String ip;
@Getter private Date firstSeen;
@Getter private Date lastSeen;
@Getter private int uses;
public IPLogEntry() {} // For Morphia
public IPLogEntry(User user, String ip) {
this.id = new ObjectId().toString();
this.user = user.getId();
this.ip = ip;
this.firstSeen = new Date();
this.lastSeen = new Date();
this.uses = 0;
}
public void used() {
this.lastSeen = new Date();
this.uses++;
APIv3.getDatastore().save(this);
}
}

View File

@ -0,0 +1,64 @@
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

@ -0,0 +1,81 @@
package net.frozenorb.apiv3.models;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
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.UUID;
@Entity(value = "punishments", noClassnameStored = true)
public final class Punishment {
@Getter @Id private String id;
@Getter @Indexed private UUID target;
@Getter private String reason;
@Getter @Indexed private PunishmentType type; // Type is indexed for the rank dump
@Getter private Date expiresAt;
@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 Punishment() {} // For Morphia
public Punishment(User target, String reason, PunishmentType type, Date expiresAt, User addedBy, Actor actor) {
this.id = new ObjectId().toString();
this.target = target.getId();
this.reason = reason;
this.type = type;
this.expiresAt = (Date) expiresAt.clone();
this.addedBy = addedBy.getId();
this.addedAt = new Date();
this.actorName = actor.getName();
this.actorType = actor.getType();
}
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.after(new Date());
}
}
public boolean isRemoved() {
return removedBy != null;
}
public enum PunishmentType {
BLACKLIST, BAN, MUTE, WARN
}
}

View File

@ -0,0 +1,44 @@
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 org.mongodb.morphia.annotations.Indexed;
import java.util.List;
@Entity(value = "ranks", noClassnameStored = true)
public final class Rank {
@Getter @Id private String id;
@Getter private int weight;
@Getter private String displayName;
@Getter private String gameColor;
@Getter private String websiteColor;
@Getter @Indexed private boolean staffRank;
public static Rank byId(String id) {
return APIv3.getDatastore().createQuery(Rank.class).field("id").equal(id).get();
}
public static List<Rank> values() {
return APIv3.getDatastore().createQuery(Rank.class).order("weight").asList();
}
public Rank() {} // For Morphia
public Rank(String id, int weight, String displayName, String gameColor, String websiteColor, boolean staffRank) {
this.id = id;
this.weight = weight;
this.displayName = displayName;
this.gameColor = gameColor;
this.websiteColor = websiteColor;
this.staffRank = staffRank;
}
public void delete() {
APIv3.getDatastore().delete(this);
}
}

View File

@ -0,0 +1,52 @@
package net.frozenorb.apiv3.models;
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 java.util.*;
@Entity(value = "servers", noClassnameStored = true)
public final class Server {
@Getter @Id private String id;
@Getter private String bungeeId;
@Getter private String displayName;
@Getter @ExcludeFromReplies String apiKey;
@Getter @Indexed private String group;
@Getter private String ip;
@Getter @Setter private Date lastUpdate;
@Getter @Setter private double lastTps;
@Getter @Setter @ExcludeFromReplies private Set<UUID> players;
public static Server byId(String id) {
return APIv3.getDatastore().createQuery(Server.class).field("_id").equal(id).get();
}
public static List<Server> values() {
return APIv3.getDatastore().createQuery(Server.class).asList();
}
public Server() {} // For Morphia
public Server(String id, String bungeeId, String displayName, String apiKey, ServerGroup group, String ip) {
this.id = id;
this.bungeeId = bungeeId;
this.displayName = displayName;
this.apiKey = apiKey;
this.group = group.getId();
this.ip = ip;
this.lastUpdate = new Date();
this.lastTps = 0;
this.players = new HashSet<>();
}
public void delete() {
APIv3.getDatastore().delete(this);
}
}

View File

@ -0,0 +1,47 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableSet;
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 java.util.*;
@Entity(value = "serverGroups", noClassnameStored = true)
public final class ServerGroup {
@Getter @Id private String id;
@Getter 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 private Map<String, List<String>> permissions = new HashMap<>();
public static ServerGroup byId(String id) {
return APIv3.getDatastore().createQuery(ServerGroup.class).field("id").equal(id).get();
}
public static List<ServerGroup> values() {
return APIv3.getDatastore().createQuery(ServerGroup.class).asList();
}
public ServerGroup() {} // For Morphia
public ServerGroup(String id, boolean isPublic) {
this.id = id;
this.isPublic = isPublic;
}
public Map<String, Boolean> calculatePermissions(Rank userRank) {
return PermissionUtils.mergeUpTo(permissions, userRank);
}
public void delete() {
APIv3.getDatastore().delete(this);
}
}

View File

@ -0,0 +1,251 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import lombok.Getter;
import lombok.Setter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
import net.frozenorb.apiv3.utils.PermissionUtils;
import net.frozenorb.apiv3.utils.TimeUtils;
import org.bson.Document;
import org.mindrot.jbcrypt.BCrypt;
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)
public final class User {
@Getter @Id private UUID id;
@Getter @Indexed private String lastUsername;
@Getter @ExcludeFromReplies private Map<String, Date> aliases;
@Getter @Setter @ExcludeFromReplies private String totpSecret;
@Getter @Indexed @ExcludeFromReplies @Setter private String emailToken;
@Getter @ExcludeFromReplies @Setter private Date emailTokenSet;
@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 firstSeen;
public static User byId(String id) {
try {
return byId(UUID.fromString(id));
} catch (Exception ex) {
return null;
}
}
public static User byId(UUID id) {
return APIv3.getDatastore().createQuery(User.class).field("id").equal(id).get();
}
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 = lastUsername;
this.aliases = new HashMap<>();
this.totpSecret = null;
this.password = null;
this.email = null;
this.phoneNumber = null;
this.lastSeenOn = null;
this.lastSeenAt = new Date();
this.firstSeen = new Date();
aliases.put(lastUsername, new Date());
}
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("target").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("ip").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("target").equal(id).asList();
}
public List<Punishment> getPunishments(Collection<Punishment.PunishmentType> types) {
return APIv3.getDatastore().createQuery(Punishment.class).field("target").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 void seenOnServer(String username, Server server) {
this.lastSeenOn = server.getId();
this.lastSeenAt = new Date();
this.aliases.put(username, new Date());
}
public void setPassword(char[] unencrypted) {
this.password = BCrypt.hashpw(new String(unencrypted), BCrypt.gensalt());
}
public boolean checkPassword(char[] unencrypted) {
return BCrypt.checkpw(new String(unencrypted), password);
}
public Rank getHighestRank() {
return getHighestRank(null);
}
public Rank getHighestRank(ServerGroup serverGroup) {
Rank highest = null;
for (Grant grant : getGrants()) {
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) {
String accessDenialReason = null;
for (Punishment punishment : getPunishments(ImmutableSet.of(
Punishment.PunishmentType.BLACKLIST,
Punishment.PunishmentType.BAN
))) {
if (!punishment.isActive()) {
continue;
}
if (punishment.getType() == Punishment.PunishmentType.BLACKLIST) {
accessDenialReason = "Your account has been blacklisted from the MineHQ Network. \n\nThis type of punishment cannot be appealed.";
} else {
accessDenialReason = "Your account has been suspended from the MineHQ Network. \n\n";
if (punishment.getExpiresAt() != null) {
accessDenialReason += "Expires in " + TimeUtils.formatIntoDetailedString(TimeUtils.getSecondsBetween(punishment.getExpiresAt(), new Date()));
} else {
accessDenialReason += "Appeal at MineHQ.com/appeal";
}
}
}
ServerGroup actorGroup = ServerGroup.byId(server.getGroup());
Rank highestRank = getHighestRank(actorGroup);
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
PermissionUtils.getDefaultPermissions(highestRank),
actorGroup.calculatePermissions(highestRank)
);
return ImmutableMap.of(
"user", this,
"access", ImmutableMap.of(
"allowed", accessDenialReason == null,
"message", accessDenialReason == null ? "Public server" : accessDenialReason
),
"rank", highestRank.getId(),
"permissions", scopedPermissions,
"totpSetup", getTotpSecret() != null
);
}
}

View File

@ -0,0 +1,37 @@
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.Document;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.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,26 +0,0 @@
package net.frozenorb.apiv3.mongoCodec;
import org.bson.BsonReader;
import org.bson.BsonWriter;
import org.bson.codecs.Codec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import java.time.Instant;
public final class InstantCodec implements Codec<Instant> {
public void encode(BsonWriter writer, Instant value, EncoderContext context) {
writer.writeDateTime(value.toEpochMilli());
}
public Instant decode(BsonReader reader, DecoderContext context) {
long epochMillis = reader.readDateTime();
return Instant.ofEpochMilli(epochMillis);
}
public Class<Instant> getEncoderClass() {
return Instant.class;
}
}

View File

@ -1,18 +0,0 @@
package net.frozenorb.apiv3.routes;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import lombok.experimental.UtilityClass;
@UtilityClass
public class AuditLogRouter {
public static Router create(Vertx vertx) {
Router router = Router.router(vertx);
return router;
}
}

View File

@ -0,0 +1,70 @@
package net.frozenorb.apiv3.routes;
import com.google.common.collect.ImmutableSet;
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 java.util.*;
public final class GETDump implements Route {
public Object handle(Request req, Response res) {
String type = req.params("type");
switch (type.toLowerCase()) {
case "blacklist":
case "ban":
case "mute":
case "warn":
List<UUID> effectedUsers = new ArrayList<>();
APIv3.getDatastore().createQuery(Punishment.class).field("type").equal(type.toUpperCase()).forEach((punishment) -> {
if (punishment.isActive()) {
effectedUsers.add(punishment.getTarget());
}
});
return effectedUsers;
case "accessDeniable":
// We have to name it effectedUsers2 because Java's
// scoping in switch statements is really dumb.
List<UUID> effectedUsers2 = new ArrayList<>();
APIv3.getDatastore().createQuery(Punishment.class).field("type").in(ImmutableSet.of(
Punishment.PunishmentType.BAN,
Punishment.PunishmentType.BLACKLIST
)).forEach((punishment) -> {
if (punishment.isActive()) {
effectedUsers2.add(punishment.getTarget());
}
});
return effectedUsers2;
case "grant":
Map<String, List<UUID>> grantDump = new HashMap<>();
APIv3.getDatastore().createQuery(Grant.class).forEach((grant) -> {
if (grant.isActive()) {
List<UUID> users = grantDump.get(grant.getRank());
if (users == null) {
users = new ArrayList<>();
grantDump.put(grant.getRank(), users);
}
users.add(grant.getTarget());
}
});
return grantDump;
default:
return ErrorUtils.invalidInput(type + " is not a valid type. Not in [blacklist, ban, mute, warn, accessDeniable, grant]");
}
}
}

View File

@ -0,0 +1,21 @@
package net.frozenorb.apiv3.routes;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.actors.Actor;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETWhoAmI implements Route {
public Object handle(Request req, Response res) {
Actor actor = req.attribute("actor");
return ImmutableMap.of(
"name", actor.getName(),
"type", actor.getType(),
"authorized", actor.isAuthorized()
);
}
}

View File

@ -1,18 +0,0 @@
package net.frozenorb.apiv3.routes;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import lombok.experimental.UtilityClass;
@UtilityClass
public class GrantsRouter {
public static Router create(Vertx vertx) {
Router router = Router.router(vertx);
return router;
}
}

View File

@ -1,18 +0,0 @@
package net.frozenorb.apiv3.routes;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import lombok.experimental.UtilityClass;
@UtilityClass
public class IPLogRouter {
public static Router create(Vertx vertx) {
Router router = Router.router(vertx);
return router;
}
}

View File

@ -0,0 +1,17 @@
package net.frozenorb.apiv3.routes;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
import spark.Spark;
public final class NotFound implements Route {
public Object handle(Request req, Response res) {
Spark.halt(404, APIv3.getGson().toJson(ErrorUtils.notFound("Route", req.url())));
return null;
}
}

View File

@ -1,18 +0,0 @@
package net.frozenorb.apiv3.routes;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import lombok.experimental.UtilityClass;
@UtilityClass
public class NotificationsRouter {
public static Router create(Vertx vertx) {
Router router = Router.router(vertx);
return router;
}
}

View File

@ -1,18 +0,0 @@
package net.frozenorb.apiv3.routes;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import lombok.experimental.UtilityClass;
@UtilityClass
public class PunishmentsRouter {
public static Router create(Vertx vertx) {
Router router = Router.router(vertx);
return router;
}
}

View File

@ -1,18 +0,0 @@
package net.frozenorb.apiv3.routes;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import lombok.experimental.UtilityClass;
@UtilityClass
public class ServerGroupsRouter {
public static Router create(Vertx vertx) {
Router router = Router.router(vertx);
return router;
}
}

View File

@ -1,87 +0,0 @@
package net.frozenorb.apiv3.routes;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import lombok.experimental.UtilityClass;
@UtilityClass
public class ServersRouter {
public static Router create(Vertx vertx) {
Router router = Router.router(vertx);
return router;
}
/*
coreHttpRouter.get("/servers").handler(ctx -> {
Servers.findAll((servers, error) -> {
if (error != null) {
ctx.response().end(ErrorUtils.createResponse(error).toJson());
} else {
ctx.response().end(JsonUtils.toLiteJsonString(servers));
}
});
});
coreHttpRouter.get("/server/:server").handler(ctx -> {
String serverId = ctx.request().getParam("server");
Servers.findById(serverId, (server, error) -> {
if (error != null) {
ctx.response().end(ErrorUtils.createResponse(error).toJson());
} else if (server != null) {
ctx.response().end(JsonUtils.toLiteJsonString(server));
} else {
ctx.response().end(ErrorUtils.createResponse("Server '" + serverId + "' not found.").toJson());
}
});
});
coreHttpRouter.get("/user/:user").handler(ctx -> {
UUID target = UUID.fromString(ctx.request().getParam("user"));
Users.findById(target.toString(), (user, error) -> {
if (error != null) {
ctx.response().end(ErrorUtils.createResponse(error).toJson());
} else if (user != null) {
ctx.response().end(JsonUtils.toLiteJsonString(user));
} else {
ctx.response().end(ErrorUtils.createResponse("User '" + target + "' not found.").toJson());
}
});
});
coreHttpRouter.get("/user/:user/grants").handler(ctx -> {
UUID target = UUID.fromString(ctx.request().getParam("user"));
Grants.findByTarget(target, (grants, error) -> {
if (error != null) {
ctx.response().end(ErrorUtils.createResponse(error).toJson());
} else {
ctx.response().end(JsonUtils.toLiteJsonString(grants));
}
});
});
coreHttpRouter.get("/auditLog").handler(ctx -> {
AuditLog.findAll((auditLogEntries, error) -> {
if (error != null) {
ctx.response().end(ErrorUtils.createResponse(error).toJson());
} else {
ctx.response().end(JsonUtils.toLiteJsonString(auditLogEntries));
}
});
});
coreHttpRouter.get("/doAction/:actionType").handler(ctx -> {
String actionType = ctx.request().getParam("actionType");
//AuditLog.log(UUID.randomUUID(), "192.168.1.103", actionType, new Document());
ctx.response().end("{'logged': true}");
});
*/
}

View File

@ -1,35 +0,0 @@
package net.frozenorb.apiv3.routes;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Router;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.accessor.Users;
import net.frozenorb.apiv3.util.ErrorUtils;
import net.frozenorb.apiv3.util.JsonUtils;
import java.util.UUID;
@UtilityClass
public class UsersRouter {
public static Router create(Vertx vertx) {
Router router = Router.router(vertx);
router.get("/info/:user").handler(ctx -> {
UUID target = UUID.fromString(ctx.request().getParam("user"));
Users.findById(target, (user, error) -> {
if (error != null) {
ctx.response().end(ErrorUtils.toResponseString(error));
} else if (user != null) {
ctx.response().end(JsonUtils.toLiteJsonString(user));
} else {
ctx.response().end(ErrorUtils.toResponseString("User '" + target + "' not found."));
}
});
});
return router;
}
}

View File

@ -0,0 +1,27 @@
package net.frozenorb.apiv3.routes.announcements;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETAnnouncements implements Route {
public Object handle(Request req, Response res) {
Actor actor = req.attribute("actor");
if (actor.getType() != ActorType.SERVER) {
return ErrorUtils.serverOnly();
}
Server sender = Server.byId(actor.getName());
ServerGroup senderGroup = ServerGroup.byId(sender.getGroup());
return senderGroup.getAnnouncements();
}
}

View File

@ -0,0 +1,23 @@
package net.frozenorb.apiv3.routes.auditLog;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.AuditLogEntry;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETAuditLog implements Route {
public Object handle(Request req, Response res) {
try {
int limit = req.queryParams("limit") == null ? 100 : Integer.parseInt(req.queryParams("limit"));
int offset = req.queryParams("offset") == null ? 0 : Integer.parseInt(req.queryParams("offset"));
return APIv3.getDatastore().createQuery(AuditLogEntry.class).order("performedAt").limit(limit).offset(offset).asList();
} catch (NumberFormatException ex) {
return ErrorUtils.invalidInput(";imit and offset must be numerical inputs.");
}
}
}

View File

@ -0,0 +1,19 @@
package net.frozenorb.apiv3.routes.chatFilterList;
import com.google.common.collect.ImmutableSet;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETChatFilterList implements Route {
public Object handle(Request req, Response res) {
return ImmutableSet.of();
}
}

View File

@ -0,0 +1,47 @@
package net.frozenorb.apiv3.routes.grants;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Permissions;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
import spark.Request;
import spark.Response;
import spark.Route;
public final class DELETEGrant implements Route {
public Object handle(Request req, Response res) {
Grant grant = Grant.byId(req.params("id"));
if (grant == null) {
return ErrorUtils.notFound("Grant", req.params("id"));
} else if (!grant.isActive()) {
return ErrorUtils.invalidInput("Cannot remove an inactive grant.");
}
User removedBy = User.byId(req.queryParams("removedBy"));
String requiredPermission = Permissions.REMOVE_GRANT + "." + grant.getRank();
if (removedBy == null) {
return ErrorUtils.notFound("User", req.queryParams("removedBy"));
} else if (!removedBy.hasPermissionAnywhere(requiredPermission)) {
return ErrorUtils.unauthorized(requiredPermission);
}
String reason = req.queryParams("reason");
if (reason == null || reason.trim().isEmpty()) {
return ErrorUtils.requiredInput("reason");
}
grant.delete(removedBy, reason);
// TODO: Fix IP
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_GRANT, ImmutableMap.of());
return grant;
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.grants;
import net.frozenorb.apiv3.models.Grant;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETGrant implements Route {
public Object handle(Request req, Response res) {
return Grant.byId(req.params("id"));
}
}

View File

@ -0,0 +1,23 @@
package net.frozenorb.apiv3.routes.grants;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETGrants implements Route {
public Object handle(Request req, Response res) {
try {
int limit = req.queryParams("limit") == null ? 100 : Integer.parseInt(req.queryParams("limit"));
int offset = req.queryParams("offset") == null ? 0 : Integer.parseInt(req.queryParams("offset"));
return APIv3.getDatastore().createQuery(Grant.class).order("addedAt").limit(limit).offset(offset).asList();
} catch (NumberFormatException ex) {
return ErrorUtils.invalidInput("limit and offset must be numerical inputs.");
}
}
}

View File

@ -0,0 +1,21 @@
package net.frozenorb.apiv3.routes.grants;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETUserGrants implements Route {
public Object handle(Request req, Response res) {
User target = User.byId(req.params("id"));
if (target == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
return target.getGrants();
}
}

View File

@ -0,0 +1,71 @@
package net.frozenorb.apiv3.routes.grants;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.Rank;
import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Permissions;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public final class POSTUserGrant implements Route {
public Object handle(Request req, Response res) {
User target = User.byId(req.params("id"));
if (target == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
String reason = req.queryParams("reason");
if (reason == null || reason.trim().isEmpty()) {
return ErrorUtils.requiredInput("reason");
}
Set<ServerGroup> scopes = new HashSet<>();
for (String serverGroupId : req.queryParams("scopes").split(",")) {
ServerGroup serverGroup = ServerGroup.byId(serverGroupId);
if (serverGroup == null) {
return ErrorUtils.notFound("Server group", serverGroupId);
}
scopes.add(serverGroup);
}
Rank rank = Rank.byId(req.queryParams("rank"));
if (rank == null) {
return ErrorUtils.notFound("Rank", req.queryParams("rank"));
}
Date expiresAt = new Date(Long.parseLong(req.queryParams("expiresAt")));
if (expiresAt.before(new Date())) {
return ErrorUtils.invalidInput("Expiration date cannot be in the past.");
}
User addedBy = User.byId(req.queryParams("addedBy"));
String requiredPermission = Permissions.CREATE_GRANT + "." + rank.getId();
if (addedBy == null) {
return ErrorUtils.notFound("User", req.queryParams("addedBy"));
} else if (!addedBy.hasPermissionAnywhere(requiredPermission)) {
return ErrorUtils.unauthorized(requiredPermission);
}
Grant grant = new Grant(target, reason, scopes, rank, expiresAt, addedBy);
APIv3.getDatastore().save(grant);
return grant;
}
}

View File

@ -0,0 +1,21 @@
package net.frozenorb.apiv3.routes.ipLog;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETUserIPLog implements Route {
public Object handle(Request req, Response res) {
User target = User.byId(req.params("id"));
if (target == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
return target.getIPLog();
}
}

View File

@ -0,0 +1,22 @@
package net.frozenorb.apiv3.routes.notificationTemplate;
import net.frozenorb.apiv3.models.NotificationTemplate;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class DELETENotificationTemplate implements Route {
public Object handle(Request req, Response res) {
NotificationTemplate notificationTemplate = NotificationTemplate.byId(req.params("id"));
if (notificationTemplate == null) {
return ErrorUtils.notFound("Notification template", req.params("id"));
}
notificationTemplate.delete();
return notificationTemplate;
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.notificationTemplate;
import net.frozenorb.apiv3.models.NotificationTemplate;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETNotificationTemplate implements Route {
public Object handle(Request req, Response res) {
return NotificationTemplate.byId(req.params("id"));
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.notificationTemplate;
import net.frozenorb.apiv3.models.NotificationTemplate;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETNotificationTemplates implements Route {
public Object handle(Request req, Response res) {
return NotificationTemplate.values();
}
}

View File

@ -0,0 +1,21 @@
package net.frozenorb.apiv3.routes.notificationTemplate;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.NotificationTemplate;
import spark.Request;
import spark.Response;
import spark.Route;
public final class POSTNotificationTemplate implements Route {
public Object handle(Request req, Response res) {
String id = req.queryParams("id");
String subject = req.queryParams("subject");
String body = req.queryParams("body");
NotificationTemplate notificationTemplate = new NotificationTemplate(id, subject, body);
APIv3.getDatastore().save(notificationTemplate);
return notificationTemplate;
}
}

View File

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

View File

@ -0,0 +1,47 @@
package net.frozenorb.apiv3.routes.punishments;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Permissions;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
import spark.Request;
import spark.Response;
import spark.Route;
public final class DELETEPunishment implements Route {
public Object handle(Request req, Response res) {
Punishment punishment = Punishment.byId(req.params("id"));
if (punishment == null) {
return ErrorUtils.notFound("Punishment", req.params("id"));
} else if (!punishment.isActive()) {
return ErrorUtils.invalidInput("Cannot remove an inactive punishment.");
}
User removedBy = User.byId(req.queryParams("removedBy"));
String requiredPermission = Permissions.REMOVE_PUNISHMENT + "." + punishment.getType().name();
if (removedBy == null) {
return ErrorUtils.notFound("User", req.queryParams("removedBy"));
} else if (!removedBy.hasPermissionAnywhere(requiredPermission)) {
return ErrorUtils.unauthorized(requiredPermission);
}
String reason = req.queryParams("reason");
if (reason == null || reason.trim().isEmpty()) {
return ErrorUtils.requiredInput("reason");
}
punishment.delete(removedBy, reason);
// TODO: Fix IP
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of());
return punishment;
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.punishments;
import net.frozenorb.apiv3.models.Punishment;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETPunishment implements Route {
public Object handle(Request req, Response res) {
return Punishment.byId(req.params("id"));
}
}

View File

@ -0,0 +1,23 @@
package net.frozenorb.apiv3.routes.punishments;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETPunishments implements Route {
public Object handle(Request req, Response res) {
try {
int limit = req.queryParams("limit") == null ? 100 : Integer.parseInt(req.queryParams("limit"));
int offset = req.queryParams("offset") == null ? 0 : Integer.parseInt(req.queryParams("offset"));
return APIv3.getDatastore().createQuery(Punishment.class).order("addedAt").limit(limit).offset(offset).asList();
} catch (NumberFormatException ex) {
return ErrorUtils.invalidInput("limit and offset must be numerical inputs.");
}
}
}

View File

@ -0,0 +1,55 @@
package net.frozenorb.apiv3.routes.punishments;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Permissions;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
import java.util.Date;
public final class POSTUserPunish implements Route {
public Object handle(Request req, Response res) {
User target = User.byId(req.params("id"));
if (target == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
String reason = req.queryParams("reason");
if (reason == null || reason.trim().isEmpty()) {
return ErrorUtils.requiredInput("reason");
}
Punishment.PunishmentType type = Punishment.PunishmentType.valueOf(req.queryParams("type"));
Date expiresAt = new Date(Long.parseLong(req.queryParams("expiresAt")));
if (expiresAt.before(new Date())) {
return ErrorUtils.invalidInput("Expiration date cannot be in the past.");
}
User addedBy = User.byId(req.queryParams("addedBy"));
String requiredPermission = Permissions.CREATE_PUNISHMENT + "." + type.name();
if (addedBy == null) {
return ErrorUtils.notFound("User", req.queryParams("addedBy"));
} else if (!addedBy.hasPermissionAnywhere(requiredPermission)) {
return ErrorUtils.unauthorized(requiredPermission);
}
if (target.hasPermissionAnywhere(Permissions.PROTECTED_PUNISHMENT)) {
return ErrorUtils.error(target.getLastSeenOn() + " is protected from punishments.");
}
Punishment punishment = new Punishment(target, reason, type, expiresAt, addedBy, req.attribute("actor"));
APIv3.getDatastore().save(punishment);
return punishment;
}
}

View File

@ -0,0 +1,22 @@
package net.frozenorb.apiv3.routes.ranks;
import net.frozenorb.apiv3.models.Rank;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class DELETERank implements Route {
public Object handle(Request req, Response res) {
Rank rank = Rank.byId(req.params("id"));
if (rank == null) {
return ErrorUtils.notFound("Rank", req.params("id"));
}
rank.delete();
return rank;
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.ranks;
import net.frozenorb.apiv3.models.Rank;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETRank implements Route {
public Object handle(Request req, Response res) {
return Rank.byId(req.params("id"));
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.ranks;
import net.frozenorb.apiv3.models.Rank;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETRanks implements Route {
public Object handle(Request req, Response res) {
return Rank.values();
}
}

View File

@ -0,0 +1,24 @@
package net.frozenorb.apiv3.routes.ranks;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Rank;
import spark.Request;
import spark.Response;
import spark.Route;
public final class POSTRank implements Route {
public Object handle(Request req, Response res) {
String id = req.queryParams("id");
int weight = Integer.parseInt(req.queryParams("weight"));
String displayName = req.queryParams("displayName");
String gameColor = req.queryParams("gameColor");
String websiteColor = req.queryParams("websiteColor");
boolean staffRank = Boolean.parseBoolean(req.queryParams("staffRank"));
Rank rank = new Rank(id, weight, displayName, gameColor, websiteColor, staffRank);
APIv3.getDatastore().save(rank);
return rank;
}
}

View File

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

View File

@ -0,0 +1,22 @@
package net.frozenorb.apiv3.routes.serverGroups;
import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class DELETEServerGroup implements Route {
public Object handle(Request req, Response res) {
ServerGroup serverGroup = ServerGroup.byId(req.params("id"));
if (serverGroup == null) {
return ErrorUtils.notFound("Server group", req.params("id"));
}
serverGroup.delete();
return serverGroup;
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.serverGroups;
import net.frozenorb.apiv3.models.ServerGroup;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETServerGroup implements Route {
public Object handle(Request req, Response res) {
return ServerGroup.byId(req.params("id"));
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.serverGroups;
import net.frozenorb.apiv3.models.ServerGroup;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETServerGroups implements Route {
public Object handle(Request req, Response res) {
return ServerGroup.values();
}
}

View File

@ -0,0 +1,20 @@
package net.frozenorb.apiv3.routes.serverGroups;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.ServerGroup;
import spark.Request;
import spark.Response;
import spark.Route;
public final class POSTServerGroup implements Route {
public Object handle(Request req, Response res) {
String id = req.queryParams("id");
boolean isPublic = Boolean.valueOf(req.queryParams("public"));
ServerGroup serverGroup = new ServerGroup(id, isPublic);
APIv3.getDatastore().save(serverGroup);
return serverGroup;
}
}

View File

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

View File

@ -0,0 +1,22 @@
package net.frozenorb.apiv3.routes.servers;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class DELETEServer implements Route {
public Object handle(Request req, Response res) {
Server server = Server.byId(req.params("id"));
if (server == null) {
return ErrorUtils.notFound("Server", req.params("id"));
}
server.delete();
return server;
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.servers;
import net.frozenorb.apiv3.models.Server;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETServer implements Route {
public Object handle(Request req, Response res) {
return Server.byId(req.params("id"));
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.servers;
import net.frozenorb.apiv3.models.Server;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETServers implements Route {
public Object handle(Request req, Response res) {
return Server.values();
}
}

View File

@ -0,0 +1,35 @@
package net.frozenorb.apiv3.routes.servers;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IPUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class POSTServer implements Route {
public Object handle(Request req, Response res) {
String id = req.queryParams("id");
String bungeeId = req.queryParams("id");
String displayName = req.queryParams("displayName");
String apiKey = req.queryParams("apiKey");
ServerGroup group = ServerGroup.byId(req.queryParams("group"));
String ip = req.queryParams("ip");
if (group == null) {
return ErrorUtils.notFound("Server group", req.queryParams("group"));
}
if (!IPUtils.isValidIP(ip)) {
return ErrorUtils.invalidInput("IP address \"" + ip + "\" is not valid.");
}
Server server = new Server(id, bungeeId, displayName, apiKey, group, ip);
APIv3.getDatastore().save(server);
return server;
}
}

View File

@ -0,0 +1,75 @@
package net.frozenorb.apiv3.routes.servers;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
import spark.Request;
import spark.Response;
import spark.Route;
import java.util.*;
public final class POSTServerHeartbeat implements Route {
@SuppressWarnings("unchecked")
public Object handle(Request req, Response res) {
Actor actor = req.attribute("actor");
if (actor.getType() != ActorType.SERVER) {
return ErrorUtils.serverOnly();
}
Server actorServer = Server.byId(actor.getName());
Document reqJson = Document.parse(req.body());
Set<UUID> onlinePlayers = new HashSet<>();
Map<String, Object> playersResponse = new HashMap<>();
for (Object player : (List<Object>) reqJson.get("players")) {
Document playerJson = (Document) player;
User user = User.byId(playerJson.getString("uuid"));
String username = playerJson.getString("username");
if (user == null) {
// Will be saved by the save command a few lines down.
user = new User(UUID.fromString(playerJson.getString("uuid")), username);
}
user.seenOnServer(username, actorServer);
APIv3.getDatastore().save(user);
onlinePlayers.add(user.getId());
playersResponse.put(user.getId().toString(), user.getLoginInfo(actorServer));
}
for (Object event : (List<Object>) reqJson.get("events")) {
Document eventJson = (Document) event;
String type = eventJson.getString("type");
switch (type) {
case "join":
break;
case "leave":
break;
case "metrics":
break;
default:
System.err.println("Recieved event with unknown type " + type + ".");
}
}
actorServer.setPlayers(onlinePlayers);
actorServer.setLastTps(reqJson.getDouble("lastTps"));
actorServer.setLastUpdate(new Date());
APIv3.getDatastore().save(actorServer);
return ImmutableMap.of(
"players", playersResponse
);
}
}

View File

@ -0,0 +1,28 @@
package net.frozenorb.apiv3.routes.servers;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class POSTServerMetrics implements Route {
public Object handle(Request req, Response res) {
Actor actor = req.attribute("actor");
if (actor.getType() != ActorType.SERVER) {
return ErrorUtils.serverOnly();
}
Server actorServer = Server.byId(actor.getName());
//LibratoBatch batch = new LibratoBatch();
return ImmutableMap.of();
}
}

View File

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

View File

@ -0,0 +1,32 @@
package net.frozenorb.apiv3.routes.users;
import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.models.UserMetaEntry;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class DELETEUserMeta implements Route {
public Object handle(Request req, Response res) {
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
ServerGroup serverGroup = ServerGroup.byId(req.params("serverGroup"));
if (serverGroup == null) {
return ErrorUtils.notFound("Server group", req.params("serverGroup"));
}
UserMetaEntry userMetaEntry = user.getMeta(serverGroup);
userMetaEntry.delete();
return userMetaEntry.getData();
}
}

View File

@ -0,0 +1,45 @@
package net.frozenorb.apiv3.routes.users;
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 java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public final class GETStaff implements Route {
public Object handle(Request req, Response res) {
Map<String, Rank> staffRanks = new HashMap<>();
Rank.values().forEach(rank -> {
if (rank.isStaffRank()) {
staffRanks.put(rank.getId(), rank);
}
});
Map<Rank, Set<User>> result = new HashMap<>();
APIv3.getDatastore().createQuery(Grant.class).field("rank").in(staffRanks.keySet()).forEach(grant -> {
if (grant.isActive()) {
User user = User.byId(grant.getTarget());
Rank rank = staffRanks.get(grant.getRank());
if (!result.containsKey(rank)) {
result.put(rank, new HashSet<>());
}
result.get(rank).add(user);
}
});
return result;
}
}

View File

@ -0,0 +1,14 @@
package net.frozenorb.apiv3.routes.users;
import net.frozenorb.apiv3.models.User;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETUser implements Route {
public Object handle(Request req, Response res) {
return User.byId(req.params("id"));
}
}

View File

@ -0,0 +1,29 @@
package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETUserDetails implements Route {
public Object handle(Request req, Response res) {
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
// Too many fields to use .of()
return ImmutableMap.builder()
.put("user", user)
.put("grants", user.getGrants())
.put("ipLog", user.getIPLog())
.put("punishments", user.getPunishments())
.put("aliases", user.getAliases())
.put("totpSetup", user.getTotpSecret() != null);
}
}

View File

@ -0,0 +1,30 @@
package net.frozenorb.apiv3.routes.users;
import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.models.UserMetaEntry;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETUserMeta implements Route {
public Object handle(Request req, Response res) {
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
ServerGroup serverGroup = ServerGroup.byId(req.params("serverGroup"));
if (serverGroup == null) {
return ErrorUtils.notFound("Server group", req.params("serverGroup"));
}
UserMetaEntry userMetaEntry = user.getMeta(serverGroup);
return userMetaEntry.getData();
}
}

View File

@ -0,0 +1,47 @@
package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.IPUtils;
import net.frozenorb.apiv3.utils.TOTPUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETUserRequiresTOTP implements Route {
public Object handle(Request req, Response res) {
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
if (user.getTotpSecret() == null) {
return ImmutableMap.of(
"required", false,
"message", "User does not have TOTP setup."
);
}
String userIp = req.queryParams("userIp");
if (!IPUtils.isValidIP(userIp)) {
return ErrorUtils.invalidInput("IP address \"" + userIp + "\" is not valid.");
}
if (TOTPUtils.isPreAuthorized(user, userIp)) {
return ImmutableMap.of(
"required", false,
"message", "User's IP has already been validated"
);
}
return ImmutableMap.of(
"required", true,
"message", "User has no TOTP exemptions."
);
}
}

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