Whoa bunch of fixes

This commit is contained in:
Colin McDonald 2016-05-01 00:34:02 -04:00
parent 5845b6d631
commit 816b214993
24 changed files with 150 additions and 36 deletions

View File

@ -82,6 +82,11 @@
<artifactId>morphia</artifactId> <artifactId>morphia</artifactId>
<version>1.1.0</version> <version>1.1.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.mongodb.morphia</groupId>
<artifactId>morphia-logging-slf4j</artifactId>
<version>1.1.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>

View File

@ -10,14 +10,14 @@ import lombok.Getter;
import net.frozenorb.apiv3.filters.ActorAttributeFilter; import net.frozenorb.apiv3.filters.ActorAttributeFilter;
import net.frozenorb.apiv3.filters.AuthorizationFilter; import net.frozenorb.apiv3.filters.AuthorizationFilter;
import net.frozenorb.apiv3.filters.ContentTypeFilter; import net.frozenorb.apiv3.filters.ContentTypeFilter;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.routes.GETDump; 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.announcements.GETAnnouncements;
import net.frozenorb.apiv3.routes.auditLog.GETAuditLog; import net.frozenorb.apiv3.routes.auditLog.GETAuditLog;
import net.frozenorb.apiv3.routes.chatFilterList.GETChatFilterList; import net.frozenorb.apiv3.routes.chatFilterList.GETChatFilterList;
import net.frozenorb.apiv3.routes.grants.DELETEGrant; import net.frozenorb.apiv3.routes.grants.*;
import net.frozenorb.apiv3.routes.grants.GETGrants;
import net.frozenorb.apiv3.routes.grants.GETUserGrants;
import net.frozenorb.apiv3.routes.grants.POSTUserGrant;
import net.frozenorb.apiv3.routes.ipLog.GETUserIPLog; import net.frozenorb.apiv3.routes.ipLog.GETUserIPLog;
import net.frozenorb.apiv3.routes.notificationTemplate.DELETENotificationTemplate; import net.frozenorb.apiv3.routes.notificationTemplate.DELETENotificationTemplate;
import net.frozenorb.apiv3.routes.notificationTemplate.GETNotificationTemplate; import net.frozenorb.apiv3.routes.notificationTemplate.GETNotificationTemplate;
@ -43,6 +43,9 @@ import net.frozenorb.apiv3.unsorted.LoggingExceptionHandler;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore; import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia; import org.mongodb.morphia.Morphia;
import org.mongodb.morphia.logging.MorphiaLoggerFactory;
import org.mongodb.morphia.logging.slf4j.SLF4JLoggerImplFactory;
import org.slf4j.impl.StaticLoggerBinder;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.InputStream; import java.io.InputStream;
@ -60,6 +63,8 @@ public final class APIv3 {
.create(); .create();
APIv3() { APIv3() {
//System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "trace");
setupConfig(); setupConfig();
setupDatabase(); setupDatabase();
setupHttp(); setupHttp();
@ -84,6 +89,9 @@ public final class APIv3 {
config.getProperty("mongo.password").toCharArray()) config.getProperty("mongo.password").toCharArray())
)); ));
MorphiaLoggerFactory.reset();
MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class);
Morphia morphia = new Morphia(); Morphia morphia = new Morphia();
morphia.mapPackage("net.frozenorb.apiv3.accessor"); morphia.mapPackage("net.frozenorb.apiv3.accessor");
@ -104,7 +112,9 @@ public final class APIv3 {
get("/auditLog", new GETAuditLog(), gson::toJson); get("/auditLog", new GETAuditLog(), gson::toJson);
get("/chatFilterList", new GETChatFilterList(), gson::toJson); get("/chatFilterList", new GETChatFilterList(), gson::toJson);
get("/dump/:type", new GETDump(), 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); get("/grants", new GETGrants(), gson::toJson);
delete("/grant/:id", new DELETEGrant(), gson::toJson); delete("/grant/:id", new DELETEGrant(), gson::toJson);
@ -151,6 +161,12 @@ public final class APIv3 {
post("/user/confirmRegister/:emailToken", new POSTUserConfirmRegister(), gson::toJson); post("/user/confirmRegister/:emailToken", new POSTUserConfirmRegister(), gson::toJson);
put("/user/:id/meta/:serverGroup", new PUTUserMeta(), gson::toJson); put("/user/:id/meta/:serverGroup", new PUTUserMeta(), gson::toJson);
delete("/user/:id/meta", new DELETEUserMeta(), gson::toJson); delete("/user/:id/meta", 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

@ -5,6 +5,7 @@ import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.*; import net.frozenorb.apiv3.actors.*;
import net.frozenorb.apiv3.models.Server; import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Filter; import spark.Filter;
import spark.Request; import spark.Request;
import spark.Response; import spark.Response;
@ -39,7 +40,7 @@ public final class ActorAttributeFilter implements Filter {
} }
} }
halt(401); halt(401, ErrorUtils.error("Failed to authorize.").toJson());
return null; return null;
} }
@ -58,6 +59,11 @@ public final class ActorAttributeFilter implements Filter {
} }
} else if (type.equals("Server") && split.length == 3) { } else if (type.equals("Server") && split.length == 3) {
Server server = Server.byId(split[1]); Server server = Server.byId(split[1]);
if (server == null) {
halt(401, ErrorUtils.notFound("Server", split[1]).toJson());
}
String givenKey = split[2]; String givenKey = split[2];
String properKey = server.getApiKey(); String properKey = server.getApiKey();
@ -67,7 +73,7 @@ public final class ActorAttributeFilter implements Filter {
} }
} }
halt(401); halt(401, ErrorUtils.error("Failed to authorize.").toJson());
return null; return null;
} }

View File

@ -10,7 +10,7 @@ import spark.Spark;
public final class AuthorizationFilter implements Filter { public final class AuthorizationFilter implements Filter {
public void handle(Request req, Response res) { public void handle(Request req, Response res) {
Actor actor = req.attribute("actors"); Actor actor = req.attribute("actor");
if (!actor.isAuthorized()) { if (!actor.isAuthorized()) {
Spark.halt(ErrorUtils.error("Unauthorized access: Please authenticate as either a server, the website, or an authorized user.").toJson()); Spark.halt(ErrorUtils.error("Unauthorized access: Please authenticate as either a server, the website, or an authorized user.").toJson());

View File

@ -16,7 +16,7 @@ public final class NotificationTemplate {
@Getter private String body; @Getter private String body;
public static NotificationTemplate byId(String id) { public static NotificationTemplate byId(String id) {
return APIv3.getDatastore().createQuery(NotificationTemplate.class).field("id").equalIgnoreCase(id).get(); return APIv3.getDatastore().createQuery(NotificationTemplate.class).field("id").equal(id).get();
} }
public static List<NotificationTemplate> values() { public static List<NotificationTemplate> values() {
@ -55,7 +55,7 @@ public final class NotificationTemplate {
String key = replacement.getKey(); String key = replacement.getKey();
String value = String.valueOf(replacement.getValue()); String value = String.valueOf(replacement.getValue());
working = working.replace(key, value); working = working.replace("%" + key + "%", value);
} }
return working; return working;

View File

@ -18,7 +18,7 @@ public final class Rank {
@Getter private boolean staffRank; @Getter private boolean staffRank;
public static Rank byId(String id) { public static Rank byId(String id) {
return APIv3.getDatastore().createQuery(Rank.class).field("id").equalIgnoreCase(id).get(); return APIv3.getDatastore().createQuery(Rank.class).field("id").equal(id).get();
} }
public static List<Rank> values() { public static List<Rank> values() {

View File

@ -3,6 +3,7 @@ package net.frozenorb.apiv3.models;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id; import org.mongodb.morphia.annotations.Id;
@ -14,15 +15,15 @@ public final class Server {
@Getter @Id private String id; @Getter @Id private String id;
@Getter private String bungeeId; @Getter private String bungeeId;
@Getter private String displayName; @Getter private String displayName;
@Getter private String apiKey; @Getter @ExcludeFromReplies String apiKey;
@Getter private String group; @Getter private String group;
@Getter private String ip; @Getter private String ip;
@Getter @Setter private Date lastUpdate; @Getter @Setter private Date lastUpdate;
@Getter @Setter private double lastTps; @Getter @Setter private double lastTps;
@Getter @Setter private Set<UUID> players; @Getter @Setter @ExcludeFromReplies private Set<UUID> players;
public static Server byId(String id) { public static Server byId(String id) {
return APIv3.getDatastore().createQuery(Server.class).field("id").equalIgnoreCase(id).get(); return APIv3.getDatastore().createQuery(Server.class).field("_id").equal(id).get();
} }
public static List<Server> values() { public static List<Server> values() {

View File

@ -14,12 +14,14 @@ public final class ServerGroup {
@Getter @Id private String id; @Getter @Id private String id;
@Getter private String displayName; @Getter private String displayName;
@Getter private Set<String> announcements; // We define these HashSets up here because, in the event they're
@Getter private Set<String> chatFilterList; // empty, Morphia will load them as null, not empty sets.
@Getter private Map<String, List<String>> permissions; @Getter private Set<String> announcements = new HashSet<>();
@Getter private Set<String> chatFilterList = new HashSet<>();
@Getter private Map<String, List<String>> permissions = new HashMap<>();
public static ServerGroup byId(String id) { public static ServerGroup byId(String id) {
return APIv3.getDatastore().createQuery(ServerGroup.class).field("id").equalIgnoreCase(id).get(); return APIv3.getDatastore().createQuery(ServerGroup.class).field("id").equal(id).get();
} }
public static List<ServerGroup> values() { public static List<ServerGroup> values() {
@ -31,9 +33,6 @@ public final class ServerGroup {
public ServerGroup(String id, String displayName) { public ServerGroup(String id, String displayName) {
this.id = id; this.id = id;
this.displayName = displayName; this.displayName = displayName;
this.announcements = new HashSet<>();
this.chatFilterList = new HashSet<>();
this.permissions = new HashMap<>();
} }
public void setAnnouncements(Set<String> announcements) { public void setAnnouncements(Set<String> announcements) {

View File

@ -34,7 +34,7 @@ public final class User {
try { try {
return byId(UUID.fromString(id)); return byId(UUID.fromString(id));
} catch (Exception ex) { } catch (Exception ex) {
throw new IllegalArgumentException("Invalid UUID string " + id, ex); return null;
} }
} }
@ -107,7 +107,7 @@ public final class User {
} }
public UserMetaEntry getMeta(ServerGroup group) { public UserMetaEntry getMeta(ServerGroup group) {
return APIv3.getDatastore().createQuery(UserMetaEntry.class).field("user").equal(id).field("serverGroup").equalIgnoreCase(group.getId()).get(); return APIv3.getDatastore().createQuery(UserMetaEntry.class).field("user").equal(id).field("serverGroup").equal(group.getId()).get();
} }
public void saveMeta(ServerGroup group, Document data) { public void saveMeta(ServerGroup group, Document data) {

View File

@ -0,0 +1,22 @@
package net.frozenorb.apiv3.routes;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.actors.Actor;
import org.bson.Document;
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

@ -0,0 +1,19 @@
package net.frozenorb.apiv3.routes;
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 spark.Spark;
public final class NotFound implements Route {
public Object handle(Request req, Response res) {
Spark.halt(404, ErrorUtils.notFound("Route", req.url()).toJson());
return null;
}
}

View File

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

View File

@ -12,7 +12,7 @@ public final class GETAuditLog implements Route {
int limit = req.queryParams("limit") == null ? 100 : Integer.parseInt(req.queryParams("limit")); int limit = req.queryParams("limit") == null ? 100 : Integer.parseInt(req.queryParams("limit"));
int offset = req.queryParams("offset") == null ? 0 : Integer.parseInt(req.queryParams("offset")); int offset = req.queryParams("offset") == null ? 0 : Integer.parseInt(req.queryParams("offset"));
return APIv3.getDatastore().createQuery(AuditLogEntry.class).order("addedAt").limit(limit).offset(offset).asList(); return APIv3.getDatastore().createQuery(AuditLogEntry.class).order("performedAt").limit(limit).offset(offset).asList();
} }
} }

View File

@ -1,7 +1,9 @@
package net.frozenorb.apiv3.routes.chatFilterList; package net.frozenorb.apiv3.routes.chatFilterList;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.models.Server; import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup; import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request; import spark.Request;
import spark.Response; import spark.Response;
import spark.Route; import spark.Route;
@ -9,7 +11,13 @@ import spark.Route;
public final class GETChatFilterList implements Route { public final class GETChatFilterList implements Route {
public Object handle(Request req, Response res) { public Object handle(Request req, Response res) {
Server sender = req.attribute("server"); Actor actor = req.attribute("actor");
if (actor.getType() != Actor.Type.SERVER) {
return ErrorUtils.serverOnly();
}
Server sender = Server.byId(actor.getName());
ServerGroup senderGroup = ServerGroup.byId(sender.getGroup()); ServerGroup senderGroup = ServerGroup.byId(sender.getGroup());
return senderGroup.getChatFilterList(); return senderGroup.getChatFilterList();

View File

@ -33,7 +33,7 @@ public final class DELETEGrant implements Route {
String reason = req.queryParams("removalReason"); String reason = req.queryParams("removalReason");
grant.delete(removedBy, reason); grant.delete(removedBy, reason);
AuditLog.log(removedBy, req.attribute("actors"), "grant.remove", new Document("grantId", grant.getId())); AuditLog.log(removedBy, req.attribute("actor"), "grant.remove", new Document("grantId", grant.getId()));
return grant; return grant;
} }

View File

@ -0,0 +1,15 @@
package net.frozenorb.apiv3.routes.grants;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.Punishment;
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

@ -12,7 +12,7 @@ public final class GETGrants implements Route {
int limit = req.queryParams("limit") == null ? 100 : Integer.parseInt(req.queryParams("limit")); int limit = req.queryParams("limit") == null ? 100 : Integer.parseInt(req.queryParams("limit"));
int offset = req.queryParams("offset") == null ? 0 : Integer.parseInt(req.queryParams("offset")); int offset = req.queryParams("offset") == null ? 0 : Integer.parseInt(req.queryParams("offset"));
return APIv3.getDatastore().createQuery(Grant.class).order("performedAt").limit(limit).offset(offset).asList(); return APIv3.getDatastore().createQuery(Grant.class).order("addedAt").limit(limit).offset(offset).asList();
} }
} }

View File

@ -33,7 +33,7 @@ public final class DELETEPunishment implements Route {
String reason = req.queryParams("removalReason"); String reason = req.queryParams("removalReason");
punishment.delete(removedBy, reason); punishment.delete(removedBy, reason);
AuditLog.log(removedBy, req.attribute("actors"), "punishment.remove", new Document("punishmentId", punishment.getId())); AuditLog.log(removedBy, req.attribute("actor"), "punishment.remove", new Document("punishmentId", punishment.getId()));
return punishment; return punishment;
} }

View File

@ -17,10 +17,10 @@ public final class POSTServerHeartbeat implements Route {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Object handle(Request req, Response res) { public Object handle(Request req, Response res) {
Actor actor = req.attribute("actors"); Actor actor = req.attribute("actor");
if (actor.getType() != Actor.Type.SERVER) { if (actor.getType() != Actor.Type.SERVER) {
return ErrorUtils.error("Heartbeats can only be performed when requested by a server."); return ErrorUtils.serverOnly();
} }
Server actorServer = Server.byId(actor.getName()); Server actorServer = Server.byId(actor.getName());

View File

@ -29,7 +29,9 @@ public final class POSTUserConfirmRegister implements Route {
return ErrorUtils.notFound("Email token", req.params("emailToken")); return ErrorUtils.notFound("Email token", req.params("emailToken"));
} }
if (user.getEmail() != null) { // We can't check email != null as that's set while we're pending
// confirmation, we have to check the token.
if (user.getEmailToken() == null) {
return ErrorUtils.error("User provided already has email set."); return ErrorUtils.error("User provided already has email set.");
} }

View File

@ -17,10 +17,10 @@ public final class POSTUserLoginInfo implements Route {
User user = User.byId(req.params("id")); User user = User.byId(req.params("id"));
String username = req.queryParams("username"); String username = req.queryParams("username");
String userIp = req.queryParams("userIp"); String userIp = req.queryParams("userIp");
Actor actor = req.attribute("actors"); Actor actor = req.attribute("actor");
if (actor.getType() != Actor.Type.SERVER) { if (actor.getType() != Actor.Type.SERVER) {
return ErrorUtils.error("Login info requests can only be performed when requested by a server."); return ErrorUtils.serverOnly();
} }
if (user == null) { if (user == null) {

View File

@ -1,17 +1,19 @@
package net.frozenorb.apiv3.unsorted; package net.frozenorb.apiv3.unsorted;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.types.ObjectId; import org.bson.types.ObjectId;
import spark.ExceptionHandler; import spark.ExceptionHandler;
import spark.Request; import spark.Request;
import spark.Response; import spark.Response;
@Slf4j
public final class LoggingExceptionHandler implements ExceptionHandler { public final class LoggingExceptionHandler implements ExceptionHandler {
public void handle(Exception ex, Request req, Response res) { public void handle(Exception ex, Request req, Response res) {
String code = new ObjectId().toHexString(); String code = new ObjectId().toHexString();
System.out.println(code + ":"); log.error(code + ":");
ex.printStackTrace(); ex.printStackTrace();
res.body(ErrorUtils.error("An unknown error has occurred. Please contact a developer with the code \"" + code + "\".").toJson()); res.body(ErrorUtils.error("An unknown error has occurred. Please contact a developer with the code \"" + code + "\".").toJson());

View File

@ -6,6 +6,10 @@ import org.bson.Document;
@UtilityClass @UtilityClass
public class ErrorUtils { public class ErrorUtils {
public static Document serverOnly() {
return error("This action can only be performed when requested by a server.");
}
public static Document notFound(String itemType, String id) { public static Document notFound(String itemType, String id) {
return error("Not found: " + itemType + " with id " + id + " cannot be found."); return error("Not found: " + itemType + " with id " + id + " cannot be found.");
} }

View File

@ -19,11 +19,18 @@ public class PermissionUtils {
return result; return result;
} }
public static Map<String, Boolean> mergeUpTo(Map<String, List<String>> collection, Rank upTo) { public static Map<String, Boolean> mergeUpTo(Map<String, List<String>> unconverted, Rank upTo) {
Map<String, Boolean> result = new HashMap<>(); Map<String, Boolean> result = new HashMap<>();
for (Rank rank : Rank.values()) { for (Rank rank : Rank.values()) {
Map<String, Boolean> rankPermissions = convertToMap(collection.get(rank.getId())); List<String> unconvertedPermissions = unconverted.get(rank.getId());
// If there's no permissions defined for this rank just skip it.
if (unconvertedPermissions == null) {
continue;
}
Map<String, Boolean> rankPermissions = convertToMap(unconvertedPermissions);
mergePermissions(result, rankPermissions); mergePermissions(result, rankPermissions);
if (upTo.getId().equals(rank.getId())) { if (upTo.getId().equals(rank.getId())) {