diff --git a/pom.xml b/pom.xml index 3e28914..d89e8b3 100644 --- a/pom.xml +++ b/pom.xml @@ -82,6 +82,11 @@ morphia 1.1.0 + + org.mongodb.morphia + morphia-logging-slf4j + 1.1.0 + org.projectlombok lombok diff --git a/src/main/java/net/frozenorb/apiv3/APIv3.java b/src/main/java/net/frozenorb/apiv3/APIv3.java index 2047ad2..20f40e1 100644 --- a/src/main/java/net/frozenorb/apiv3/APIv3.java +++ b/src/main/java/net/frozenorb/apiv3/APIv3.java @@ -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); } } \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/filters/ActorAttributeFilter.java b/src/main/java/net/frozenorb/apiv3/filters/ActorAttributeFilter.java index d0cfb4a..ed2bb19 100644 --- a/src/main/java/net/frozenorb/apiv3/filters/ActorAttributeFilter.java +++ b/src/main/java/net/frozenorb/apiv3/filters/ActorAttributeFilter.java @@ -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; } diff --git a/src/main/java/net/frozenorb/apiv3/filters/AuthorizationFilter.java b/src/main/java/net/frozenorb/apiv3/filters/AuthorizationFilter.java index c02edc4..d961722 100644 --- a/src/main/java/net/frozenorb/apiv3/filters/AuthorizationFilter.java +++ b/src/main/java/net/frozenorb/apiv3/filters/AuthorizationFilter.java @@ -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()); diff --git a/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java b/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java index c469948..1cffe9f 100644 --- a/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java +++ b/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java @@ -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 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; diff --git a/src/main/java/net/frozenorb/apiv3/models/Rank.java b/src/main/java/net/frozenorb/apiv3/models/Rank.java index 6a91639..cefd54e 100644 --- a/src/main/java/net/frozenorb/apiv3/models/Rank.java +++ b/src/main/java/net/frozenorb/apiv3/models/Rank.java @@ -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 values() { diff --git a/src/main/java/net/frozenorb/apiv3/models/Server.java b/src/main/java/net/frozenorb/apiv3/models/Server.java index de16b4f..c7ca776 100644 --- a/src/main/java/net/frozenorb/apiv3/models/Server.java +++ b/src/main/java/net/frozenorb/apiv3/models/Server.java @@ -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 players; + @Getter @Setter @ExcludeFromReplies private Set 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 values() { diff --git a/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java b/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java index 513a1be..a322627 100644 --- a/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java +++ b/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java @@ -14,12 +14,14 @@ public final class ServerGroup { @Getter @Id private String id; @Getter private String displayName; - @Getter private Set announcements; - @Getter private Set chatFilterList; - @Getter private Map> 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 announcements = new HashSet<>(); + @Getter private Set chatFilterList = new HashSet<>(); + @Getter private Map> 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 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 announcements) { diff --git a/src/main/java/net/frozenorb/apiv3/models/User.java b/src/main/java/net/frozenorb/apiv3/models/User.java index 664516b..5d628ab 100644 --- a/src/main/java/net/frozenorb/apiv3/models/User.java +++ b/src/main/java/net/frozenorb/apiv3/models/User.java @@ -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) { diff --git a/src/main/java/net/frozenorb/apiv3/routes/GETWhoAmI.java b/src/main/java/net/frozenorb/apiv3/routes/GETWhoAmI.java new file mode 100644 index 0000000..4f63eee --- /dev/null +++ b/src/main/java/net/frozenorb/apiv3/routes/GETWhoAmI.java @@ -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() + ); + } + +} \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/routes/NotFound.java b/src/main/java/net/frozenorb/apiv3/routes/NotFound.java new file mode 100644 index 0000000..39572f6 --- /dev/null +++ b/src/main/java/net/frozenorb/apiv3/routes/NotFound.java @@ -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; + } + +} \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/routes/announcements/GETAnnouncements.java b/src/main/java/net/frozenorb/apiv3/routes/announcements/GETAnnouncements.java index 492d51e..5f3cdea 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/announcements/GETAnnouncements.java +++ b/src/main/java/net/frozenorb/apiv3/routes/announcements/GETAnnouncements.java @@ -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(); diff --git a/src/main/java/net/frozenorb/apiv3/routes/auditLog/GETAuditLog.java b/src/main/java/net/frozenorb/apiv3/routes/auditLog/GETAuditLog.java index 4c557e8..cfa987a 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/auditLog/GETAuditLog.java +++ b/src/main/java/net/frozenorb/apiv3/routes/auditLog/GETAuditLog.java @@ -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(); } } \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/routes/chatFilterList/GETChatFilterList.java b/src/main/java/net/frozenorb/apiv3/routes/chatFilterList/GETChatFilterList.java index 0c3b60b..6f670e8 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/chatFilterList/GETChatFilterList.java +++ b/src/main/java/net/frozenorb/apiv3/routes/chatFilterList/GETChatFilterList.java @@ -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(); diff --git a/src/main/java/net/frozenorb/apiv3/routes/grants/DELETEGrant.java b/src/main/java/net/frozenorb/apiv3/routes/grants/DELETEGrant.java index 2cbaa92..8d67796 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/grants/DELETEGrant.java +++ b/src/main/java/net/frozenorb/apiv3/routes/grants/DELETEGrant.java @@ -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; } diff --git a/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrant.java b/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrant.java new file mode 100644 index 0000000..1f612b2 --- /dev/null +++ b/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrant.java @@ -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")); + } + +} \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrants.java b/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrants.java index e3f9ea6..33a03c0 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrants.java +++ b/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrants.java @@ -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(); } } \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/routes/punishments/DELETEPunishment.java b/src/main/java/net/frozenorb/apiv3/routes/punishments/DELETEPunishment.java index 6ae326f..072b3f5 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/punishments/DELETEPunishment.java +++ b/src/main/java/net/frozenorb/apiv3/routes/punishments/DELETEPunishment.java @@ -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; } diff --git a/src/main/java/net/frozenorb/apiv3/routes/servers/POSTServerHeartbeat.java b/src/main/java/net/frozenorb/apiv3/routes/servers/POSTServerHeartbeat.java index 1eb704b..3a6e8b7 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/servers/POSTServerHeartbeat.java +++ b/src/main/java/net/frozenorb/apiv3/routes/servers/POSTServerHeartbeat.java @@ -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()); diff --git a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserConfirmRegister.java b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserConfirmRegister.java index 76e002f..2486890 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserConfirmRegister.java +++ b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserConfirmRegister.java @@ -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."); } diff --git a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserLoginInfo.java b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserLoginInfo.java index c8486ca..9597fc3 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserLoginInfo.java +++ b/src/main/java/net/frozenorb/apiv3/routes/users/POSTUserLoginInfo.java @@ -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) { diff --git a/src/main/java/net/frozenorb/apiv3/unsorted/LoggingExceptionHandler.java b/src/main/java/net/frozenorb/apiv3/unsorted/LoggingExceptionHandler.java index d016e3b..e9a8a03 100644 --- a/src/main/java/net/frozenorb/apiv3/unsorted/LoggingExceptionHandler.java +++ b/src/main/java/net/frozenorb/apiv3/unsorted/LoggingExceptionHandler.java @@ -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()); diff --git a/src/main/java/net/frozenorb/apiv3/utils/ErrorUtils.java b/src/main/java/net/frozenorb/apiv3/utils/ErrorUtils.java index af980e7..6963440 100644 --- a/src/main/java/net/frozenorb/apiv3/utils/ErrorUtils.java +++ b/src/main/java/net/frozenorb/apiv3/utils/ErrorUtils.java @@ -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."); } diff --git a/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java b/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java index e82c280..92ab201 100644 --- a/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java +++ b/src/main/java/net/frozenorb/apiv3/utils/PermissionUtils.java @@ -19,11 +19,18 @@ public class PermissionUtils { return result; } - public static Map mergeUpTo(Map> collection, Rank upTo) { + public static Map mergeUpTo(Map> unconverted, Rank upTo) { Map result = new HashMap<>(); for (Rank rank : Rank.values()) { - Map rankPermissions = convertToMap(collection.get(rank.getId())); + List unconvertedPermissions = unconverted.get(rank.getId()); + + // If there's no permissions defined for this rank just skip it. + if (unconvertedPermissions == null) { + continue; + } + + Map rankPermissions = convertToMap(unconvertedPermissions); mergePermissions(result, rankPermissions); if (upTo.getId().equals(rank.getId())) {