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>
<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.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@ -10,14 +10,14 @@ import lombok.Getter;
import net.frozenorb.apiv3.filters.ActorAttributeFilter;
import net.frozenorb.apiv3.filters.AuthorizationFilter;
import net.frozenorb.apiv3.filters.ContentTypeFilter;
import net.frozenorb.apiv3.models.Server;
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.DELETEGrant;
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.grants.*;
import net.frozenorb.apiv3.routes.ipLog.GETUserIPLog;
import net.frozenorb.apiv3.routes.notificationTemplate.DELETENotificationTemplate;
import net.frozenorb.apiv3.routes.notificationTemplate.GETNotificationTemplate;
@ -43,6 +43,9 @@ import net.frozenorb.apiv3.unsorted.LoggingExceptionHandler;
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 org.slf4j.impl.StaticLoggerBinder;
import java.io.FileInputStream;
import java.io.InputStream;
@ -60,6 +63,8 @@ public final class APIv3 {
.create();
APIv3() {
//System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "trace");
setupConfig();
setupDatabase();
setupHttp();
@ -84,6 +89,9 @@ public final class APIv3 {
config.getProperty("mongo.password").toCharArray())
));
MorphiaLoggerFactory.reset();
MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class);
Morphia morphia = new Morphia();
morphia.mapPackage("net.frozenorb.apiv3.accessor");
@ -104,7 +112,9 @@ public final class APIv3 {
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);
@ -151,6 +161,12 @@ public final class APIv3 {
post("/user/confirmRegister/:emailToken", new POSTUserConfirmRegister(), gson::toJson);
put("/user/:id/meta/:serverGroup", new PUTUserMeta(), 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.models.Server;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Filter;
import spark.Request;
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;
}
@ -58,6 +59,11 @@ public final class ActorAttributeFilter implements Filter {
}
} else if (type.equals("Server") && split.length == 3) {
Server server = Server.byId(split[1]);
if (server == null) {
halt(401, ErrorUtils.notFound("Server", split[1]).toJson());
}
String givenKey = split[2];
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;
}

View File

@ -10,7 +10,7 @@ import spark.Spark;
public final class AuthorizationFilter implements Filter {
public void handle(Request req, Response res) {
Actor actor = req.attribute("actors");
Actor actor = req.attribute("actor");
if (!actor.isAuthorized()) {
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;
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() {
@ -55,7 +55,7 @@ public final class NotificationTemplate {
String key = replacement.getKey();
String value = String.valueOf(replacement.getValue());
working = working.replace(key, value);
working = working.replace("%" + key + "%", value);
}
return working;

View File

@ -18,7 +18,7 @@ public final class Rank {
@Getter private boolean staffRank;
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() {

View File

@ -3,6 +3,7 @@ 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;
@ -14,15 +15,15 @@ public final class Server {
@Getter @Id private String id;
@Getter private String bungeeId;
@Getter private String displayName;
@Getter private String apiKey;
@Getter @ExcludeFromReplies String apiKey;
@Getter private String group;
@Getter private String ip;
@Getter @Setter private Date lastUpdate;
@Getter @Setter private double lastTps;
@Getter @Setter private Set<UUID> players;
@Getter @Setter @ExcludeFromReplies private Set<UUID> players;
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() {

View File

@ -14,12 +14,14 @@ public final class ServerGroup {
@Getter @Id private String id;
@Getter private String displayName;
@Getter private Set<String> announcements;
@Getter private Set<String> chatFilterList;
@Getter private Map<String, List<String>> permissions;
// We define these HashSets up here because, in the event they're
// empty, Morphia will load them as null, not empty sets.
@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) {
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() {
@ -31,9 +33,6 @@ public final class ServerGroup {
public ServerGroup(String id, String displayName) {
this.id = id;
this.displayName = displayName;
this.announcements = new HashSet<>();
this.chatFilterList = new HashSet<>();
this.permissions = new HashMap<>();
}
public void setAnnouncements(Set<String> announcements) {

View File

@ -34,7 +34,7 @@ public final class User {
try {
return byId(UUID.fromString(id));
} 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) {
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) {

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;
import net.frozenorb.apiv3.actors.Actor;
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;
@ -9,7 +11,13 @@ import spark.Route;
public final class GETAnnouncements implements Route {
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());
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 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;
import net.frozenorb.apiv3.actors.Actor;
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;
@ -9,7 +11,13 @@ import spark.Route;
public final class GETChatFilterList implements Route {
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());
return senderGroup.getChatFilterList();

View File

@ -33,7 +33,7 @@ public final class DELETEGrant implements Route {
String reason = req.queryParams("removalReason");
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;
}

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 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");
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;
}

View File

@ -17,10 +17,10 @@ public final class POSTServerHeartbeat implements Route {
@SuppressWarnings("unchecked")
public Object handle(Request req, Response res) {
Actor actor = req.attribute("actors");
Actor actor = req.attribute("actor");
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());

View File

@ -29,7 +29,9 @@ public final class POSTUserConfirmRegister implements Route {
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.");
}

View File

@ -17,10 +17,10 @@ public final class POSTUserLoginInfo implements Route {
User user = User.byId(req.params("id"));
String username = req.queryParams("username");
String userIp = req.queryParams("userIp");
Actor actor = req.attribute("actors");
Actor actor = req.attribute("actor");
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) {

View File

@ -1,17 +1,19 @@
package net.frozenorb.apiv3.unsorted;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.types.ObjectId;
import spark.ExceptionHandler;
import spark.Request;
import spark.Response;
@Slf4j
public final class LoggingExceptionHandler implements ExceptionHandler {
public void handle(Exception ex, Request req, Response res) {
String code = new ObjectId().toHexString();
System.out.println(code + ":");
log.error(code + ":");
ex.printStackTrace();
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
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) {
return error("Not found: " + itemType + " with id " + id + " cannot be found.");
}

View File

@ -19,11 +19,18 @@ public class PermissionUtils {
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<>();
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);
if (upTo.getId().equals(rank.getId())) {