From fb12a1352884ae1537a861a0718d4daacdcfea20 Mon Sep 17 00:00:00 2001 From: Colin McDonald Date: Sun, 17 Apr 2016 15:23:02 -0400 Subject: [PATCH] Push a bunch of changes --- src/main/java/net/frozenorb/apiv3/APIv3.java | 12 +++++---- .../frozenorb/apiv3/models/AuditLogEntry.java | 10 +++----- .../net/frozenorb/apiv3/models/Grant.java | 7 ++++-- .../net/frozenorb/apiv3/models/IPBan.java | 3 ++- .../frozenorb/apiv3/models/IPLogEntry.java | 2 +- .../apiv3/models/NotificationLogEntry.java | 6 ++--- .../apiv3/models/NotificationTemplate.java | 4 +-- .../frozenorb/apiv3/models/Punishment.java | 6 ++--- .../java/net/frozenorb/apiv3/models/Rank.java | 2 +- .../net/frozenorb/apiv3/models/Server.java | 7 ++++-- .../frozenorb/apiv3/models/ServerGroup.java | 2 +- .../java/net/frozenorb/apiv3/models/User.java | 13 +++++++++- .../apiv3/routes/grants/DELETEGrant.java | 25 ++++++++++++++++--- .../apiv3/routes/grants/GETGrants.java | 2 -- .../apiv3/routes/grants/POSTUserGrant.java | 4 +-- .../routes/punishments/DELETEPunishment.java | 6 ++++- .../routes/punishments/POSTUserPunish.java | 2 +- .../apiv3/routes/servers/GETServers.java | 1 + .../frozenorb/apiv3/weirdStuff/AuditLog.java | 20 +++++++++++++++ .../apiv3/weirdStuff/ErrorUtils.java | 21 ++++++++++++++++ .../apiv3/weirdStuff/ObjectIdTypeAdapter.java | 3 +++ .../apiv3/weirdStuff/Permissions.java | 10 ++++++++ 22 files changed, 131 insertions(+), 37 deletions(-) create mode 100644 src/main/java/net/frozenorb/apiv3/weirdStuff/AuditLog.java create mode 100644 src/main/java/net/frozenorb/apiv3/weirdStuff/ErrorUtils.java create mode 100644 src/main/java/net/frozenorb/apiv3/weirdStuff/Permissions.java diff --git a/src/main/java/net/frozenorb/apiv3/APIv3.java b/src/main/java/net/frozenorb/apiv3/APIv3.java index bd3326e..3bec6ad 100644 --- a/src/main/java/net/frozenorb/apiv3/APIv3.java +++ b/src/main/java/net/frozenorb/apiv3/APIv3.java @@ -7,7 +7,7 @@ import com.mongodb.MongoClient; import com.mongodb.MongoCredential; import com.mongodb.ServerAddress; import lombok.Getter; -import net.frozenorb.apiv3.models.*; +import net.frozenorb.apiv3.models.ServerGroup; import net.frozenorb.apiv3.routes.announcements.GETAnnouncements; import net.frozenorb.apiv3.routes.chatFilterList.GETChatFilterList; import net.frozenorb.apiv3.routes.grants.DELETEGrant; @@ -18,9 +18,9 @@ import net.frozenorb.apiv3.routes.punishments.GETPunishments; import net.frozenorb.apiv3.routes.punishments.POSTUserPunish; import net.frozenorb.apiv3.routes.servers.GETServer; import net.frozenorb.apiv3.routes.servers.GETServers; +import net.frozenorb.apiv3.routes.users.GETStaff; import net.frozenorb.apiv3.routes.users.GETUser; import net.frozenorb.apiv3.routes.users.GETUsers; -import net.frozenorb.apiv3.routes.users.GETStaff; import net.frozenorb.apiv3.weirdStuff.ActorAttributeFilter; import net.frozenorb.apiv3.weirdStuff.ContentTypeFilter; import net.frozenorb.apiv3.weirdStuff.FollowAnnotationExclusionStrategy; @@ -29,8 +29,6 @@ import org.bson.types.ObjectId; import org.mongodb.morphia.Datastore; import org.mongodb.morphia.Morphia; -import java.util.*; - import static spark.Spark.*; public final class APIv3 { @@ -54,6 +52,10 @@ public final class APIv3 { datastore = morphia.createDatastore(mongoClient, "minehqapi"); datastore.ensureIndexes(); + + if (ServerGroup.byId("Website") == null) { + datastore.save(new ServerGroup("Website", "Website")); + } } private void setupHttp() { @@ -76,7 +78,7 @@ public final class APIv3 { get("/servers", new GETServers(), gson::toJson); get("/user/:id", new GETUser(), gson::toJson); - get("/users", new GETUsers() gson::toJson); + get("/users", new GETUsers(), gson::toJson); get("/staff", new GETStaff(), gson::toJson); after(new ContentTypeFilter()); diff --git a/src/main/java/net/frozenorb/apiv3/models/AuditLogEntry.java b/src/main/java/net/frozenorb/apiv3/models/AuditLogEntry.java index 77f79fc..11b89c2 100644 --- a/src/main/java/net/frozenorb/apiv3/models/AuditLogEntry.java +++ b/src/main/java/net/frozenorb/apiv3/models/AuditLogEntry.java @@ -12,24 +12,22 @@ import java.util.UUID; @Entity(value = "auditLog", noClassnameStored = true) public final class AuditLogEntry { - @Id private ObjectId id; + @Getter @Id private ObjectId id; @Getter private UUID performedBy; @Getter private Date performedAt; @Getter private String performedFrom; @Getter private String actionType; + @Getter private String description; @Getter private Document actionData; public AuditLogEntry() {} // For Morphia - public AuditLogEntry(User performedBy, String performedFromIp, String actionType) { - this(performedBy, performedFromIp, actionType, new Document()); - } - - public AuditLogEntry(User performedBy, String performedFrom, String actionType, Document actionData) { + public AuditLogEntry(User performedBy, String performedFrom, String actionType, String description, Document actionData) { this.performedBy = performedBy.getId(); this.performedAt = new Date(); this.performedFrom = performedFrom; this.actionType = actionType; + this.description = description; this.actionData = actionData; } diff --git a/src/main/java/net/frozenorb/apiv3/models/Grant.java b/src/main/java/net/frozenorb/apiv3/models/Grant.java index a617177..1017616 100644 --- a/src/main/java/net/frozenorb/apiv3/models/Grant.java +++ b/src/main/java/net/frozenorb/apiv3/models/Grant.java @@ -7,12 +7,15 @@ import org.bson.types.ObjectId; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; -import java.util.*; +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 { - @Id private ObjectId id; + @Getter @Id private ObjectId id; @Getter private UUID target; @Getter private String reason; @Getter private Set scopes; diff --git a/src/main/java/net/frozenorb/apiv3/models/IPBan.java b/src/main/java/net/frozenorb/apiv3/models/IPBan.java index 40cf4cf..15621ad 100644 --- a/src/main/java/net/frozenorb/apiv3/models/IPBan.java +++ b/src/main/java/net/frozenorb/apiv3/models/IPBan.java @@ -1,5 +1,6 @@ package net.frozenorb.apiv3.models; +import lombok.Getter; import org.bson.types.ObjectId; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; @@ -7,7 +8,7 @@ import org.mongodb.morphia.annotations.Id; @Entity(value = "ipBans", noClassnameStored = true) public final class IPBan { - @Id private ObjectId id; + @Getter @Id private ObjectId id; public IPBan() {} // For Morphia diff --git a/src/main/java/net/frozenorb/apiv3/models/IPLogEntry.java b/src/main/java/net/frozenorb/apiv3/models/IPLogEntry.java index 0f6751f..8007a4d 100644 --- a/src/main/java/net/frozenorb/apiv3/models/IPLogEntry.java +++ b/src/main/java/net/frozenorb/apiv3/models/IPLogEntry.java @@ -12,7 +12,7 @@ import java.util.UUID; @Entity(value = "ipLog", noClassnameStored = true) public final class IPLogEntry { - @Id private ObjectId id; + @Getter @Id private ObjectId id; @Getter private UUID user; @Getter private String ip; @Getter private Date firstSeen; diff --git a/src/main/java/net/frozenorb/apiv3/models/NotificationLogEntry.java b/src/main/java/net/frozenorb/apiv3/models/NotificationLogEntry.java index 4a1b0c9..28370b5 100644 --- a/src/main/java/net/frozenorb/apiv3/models/NotificationLogEntry.java +++ b/src/main/java/net/frozenorb/apiv3/models/NotificationLogEntry.java @@ -11,7 +11,7 @@ import java.util.UUID; @Entity(value = "notificationLog", noClassnameStored = true) public final class NotificationLogEntry { - @Id private ObjectId id; + @Getter @Id private ObjectId id; @Getter private UUID target; @Getter private Date sentAt; @Getter private NotificationType type; @@ -20,8 +20,8 @@ public final class NotificationLogEntry { public NotificationLogEntry() {} // For Morphia - public NotificationLogEntry(UUID target, NotificationType type, String title, String body) { - this.target = target; + public NotificationLogEntry(User target, NotificationType type, String title, String body) { + this.target = target.getId(); this.sentAt = new Date(); this.type = type; this.title = title; diff --git a/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java b/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java index e141af2..3245c8d 100644 --- a/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java +++ b/src/main/java/net/frozenorb/apiv3/models/NotificationTemplate.java @@ -12,14 +12,14 @@ import java.util.UUID; @Entity(value = "notificationTemplates", noClassnameStored = true) public final class NotificationTemplate { - @Id private String id; + @Getter @Id private String id; @Getter private String title; @Getter private String body; @Getter private Date lastUpdatedAt; @Getter private UUID lastUpdatedBy; public static NotificationTemplate byId(String id) { - return APIv3.getDatastore().createQuery(NotificationTemplate.class).field("id").equal(id).get(); + return APIv3.getDatastore().createQuery(NotificationTemplate.class).field("id").equalIgnoreCase(id).get(); } public NotificationTemplate() {} // For Morphia diff --git a/src/main/java/net/frozenorb/apiv3/models/Punishment.java b/src/main/java/net/frozenorb/apiv3/models/Punishment.java index ddc4d70..c3c8ff8 100644 --- a/src/main/java/net/frozenorb/apiv3/models/Punishment.java +++ b/src/main/java/net/frozenorb/apiv3/models/Punishment.java @@ -12,7 +12,7 @@ import java.util.UUID; @Entity(value = "punishments", noClassnameStored = true) public final class Punishment { - @Id private ObjectId id; + @Getter @Id private ObjectId id; @Getter private UUID target; @Getter private String reason; @Getter private PunishmentType type; @@ -42,8 +42,8 @@ public final class Punishment { this.addedOn = addedOn.getId(); } - public void delete(UUID removedBy, String reason) { - this.removedBy = removedBy; + public void delete(User removedBy, String reason) { + this.removedBy = removedBy.getId(); this.removedAt = new Date(); this.removalReason = reason; diff --git a/src/main/java/net/frozenorb/apiv3/models/Rank.java b/src/main/java/net/frozenorb/apiv3/models/Rank.java index 350bd10..416c68e 100644 --- a/src/main/java/net/frozenorb/apiv3/models/Rank.java +++ b/src/main/java/net/frozenorb/apiv3/models/Rank.java @@ -16,7 +16,7 @@ public final class Rank { @Getter private boolean staffRank; public static Rank byId(String id) { - return APIv3.getDatastore().createQuery(Rank.class).field("id").equal(id).get(); + return APIv3.getDatastore().createQuery(Rank.class).field("id").equalIgnoreCase(id).get(); } public Rank() {} // For Morphia diff --git a/src/main/java/net/frozenorb/apiv3/models/Server.java b/src/main/java/net/frozenorb/apiv3/models/Server.java index 844622c..33b089e 100644 --- a/src/main/java/net/frozenorb/apiv3/models/Server.java +++ b/src/main/java/net/frozenorb/apiv3/models/Server.java @@ -5,7 +5,10 @@ import net.frozenorb.apiv3.APIv3; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; -import java.util.*; +import java.util.Date; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; @Entity(value = "servers", noClassnameStored = true) public final class Server { @@ -21,7 +24,7 @@ public final class Server { @Getter private Set players; public static Server byId(String id) { - return APIv3.getDatastore().createQuery(Server.class).field("id").equal(id).get(); + return APIv3.getDatastore().createQuery(Server.class).field("id").equalIgnoreCase(id).get(); } public Server() {} // For Morphia diff --git a/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java b/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java index 7dbd5bb..6001c41 100644 --- a/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java +++ b/src/main/java/net/frozenorb/apiv3/models/ServerGroup.java @@ -17,7 +17,7 @@ public final class ServerGroup { @Getter private Set chatFilterList; public static ServerGroup byId(String id) { - return APIv3.getDatastore().createQuery(ServerGroup.class).field("id").equal(id).get(); + return APIv3.getDatastore().createQuery(ServerGroup.class).field("id").equalIgnoreCase(id).get(); } public ServerGroup() {} // For Morphia diff --git a/src/main/java/net/frozenorb/apiv3/models/User.java b/src/main/java/net/frozenorb/apiv3/models/User.java index 6582aca..ead4320 100644 --- a/src/main/java/net/frozenorb/apiv3/models/User.java +++ b/src/main/java/net/frozenorb/apiv3/models/User.java @@ -40,7 +40,7 @@ public final class User { @Deprecated public static User byName(String name) { - return APIv3.getDatastore().createQuery(User.class).field("lastName").equal(name).get(); + return APIv3.getDatastore().createQuery(User.class).field("lastName").equalIgnoreCase(name).get(); } public User() {} // For Morphia @@ -61,4 +61,15 @@ public final class User { aliases.put(lastName, new Date()); } + public boolean hasPermissionAnywhere(String permission) { + return hasPermissionScoped(permission, null); + } + + public boolean hasPermissionScoped(String permission, ServerGroup scope) { + // TODO: BLAH FIX THIS IF WE DONT REMOVE THEN IDK WHAT TO SAY + // Also this is 1 > 0 because 'return true;' means all usages of this + // get marked as a warning until we change it. + return 1 > 0; + } + } \ No newline at end of file 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 fcd2bd5..29c0ec7 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/grants/DELETEGrant.java +++ b/src/main/java/net/frozenorb/apiv3/routes/grants/DELETEGrant.java @@ -1,9 +1,11 @@ package net.frozenorb.apiv3.routes.grants; import net.frozenorb.apiv3.models.Grant; -import net.frozenorb.apiv3.models.Server; -import net.frozenorb.apiv3.models.ServerGroup; import net.frozenorb.apiv3.models.User; +import net.frozenorb.apiv3.weirdStuff.AuditLog; +import net.frozenorb.apiv3.weirdStuff.ErrorUtils; +import net.frozenorb.apiv3.weirdStuff.Permissions; +import org.bson.Document; import spark.Request; import spark.Response; import spark.Route; @@ -14,10 +16,27 @@ 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.error("Cannot remove an inactive grant."); + } + User removedBy = User.byId(UUID.fromString(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 removedByIp = req.queryParams("removedByIp"); String reason = req.queryParams("removalReason"); - grant.delete(removedBy.getId(), reason); + grant.delete(removedBy, reason); + AuditLog.log(removedBy, removedByIp, "grant.remove", "Removed a " + grant.getRank() + " grant from " + grant.getTarget() + " for \" " + reason + "\"", new Document("grantId", grant.getId())); return grant; } 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 340682f..afa0514 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrants.java +++ b/src/main/java/net/frozenorb/apiv3/routes/grants/GETGrants.java @@ -2,8 +2,6 @@ package net.frozenorb.apiv3.routes.grants; import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.models.Grant; -import net.frozenorb.apiv3.models.Server; -import net.frozenorb.apiv3.models.ServerGroup; import spark.Request; import spark.Response; import spark.Route; diff --git a/src/main/java/net/frozenorb/apiv3/routes/grants/POSTUserGrant.java b/src/main/java/net/frozenorb/apiv3/routes/grants/POSTUserGrant.java index 2429dda..6ca7c98 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/grants/POSTUserGrant.java +++ b/src/main/java/net/frozenorb/apiv3/routes/grants/POSTUserGrant.java @@ -18,9 +18,9 @@ public final class POSTUserGrant implements Route { Set scopes = new HashSet<>(Arrays.asList(req.queryParams("scopes").split(","))); Rank rank = Rank.byId(req.queryParams("rank")); Date expiresAt = new Date(Long.parseLong(req.queryParams("expiresAt"))); - User createdBy = User.byId(UUID.fromString(req.queryParams("createdBy"))); + User addedBy = User.byId(UUID.fromString(req.queryParams("addedBy"))); - Grant grant = new Grant(target.getId(), reason, scopes, rank.getId(), expiresAt, createdBy.getId()); + Grant grant = new Grant(target, reason, scopes, rank, expiresAt, addedBy); APIv3.getDatastore().save(grant); return grant; } 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 a50e0d8..10299ca 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/punishments/DELETEPunishment.java +++ b/src/main/java/net/frozenorb/apiv3/routes/punishments/DELETEPunishment.java @@ -2,6 +2,8 @@ package net.frozenorb.apiv3.routes.punishments; import net.frozenorb.apiv3.models.Punishment; import net.frozenorb.apiv3.models.User; +import net.frozenorb.apiv3.weirdStuff.AuditLog; +import org.bson.Document; import spark.Request; import spark.Response; import spark.Route; @@ -13,9 +15,11 @@ public final class DELETEPunishment implements Route { public Object handle(Request req, Response res) { Punishment punishment = Punishment.byId(req.params("id")); User removedBy = User.byId(UUID.fromString(req.queryParams("removedBy"))); + String removedByIp = req.queryParams("removedByIp"); String reason = req.queryParams("removalReason"); - punishment.delete(removedBy.getId(), reason); + punishment.delete(removedBy, reason); + AuditLog.log(removedBy, removedByIp, "punishment.remove", "Removed a " + punishment.getType().name().toLowerCase() + " from " + punishment.getTarget() + " for \" " + reason + "\"", new Document("punishmentId", punishment.getId())); return punishment; } diff --git a/src/main/java/net/frozenorb/apiv3/routes/punishments/POSTUserPunish.java b/src/main/java/net/frozenorb/apiv3/routes/punishments/POSTUserPunish.java index e024aa0..16e34e3 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/punishments/POSTUserPunish.java +++ b/src/main/java/net/frozenorb/apiv3/routes/punishments/POSTUserPunish.java @@ -21,7 +21,7 @@ public final class POSTUserPunish implements Route { User createdBy = User.byId(UUID.fromString(req.queryParams("createdBy"))); Server addedOn = Server.byId(req.queryParams("addedOn")); - Punishment punishment = new Punishment(target.getId(), reason, type, expiresAt, createdBy.getId(), addedOn.getId()); + Punishment punishment = new Punishment(target, reason, type, expiresAt, createdBy, addedOn); APIv3.getDatastore().save(punishment); return punishment; } diff --git a/src/main/java/net/frozenorb/apiv3/routes/servers/GETServers.java b/src/main/java/net/frozenorb/apiv3/routes/servers/GETServers.java index 061fab9..29f9311 100644 --- a/src/main/java/net/frozenorb/apiv3/routes/servers/GETServers.java +++ b/src/main/java/net/frozenorb/apiv3/routes/servers/GETServers.java @@ -1,5 +1,6 @@ package net.frozenorb.apiv3.routes.servers; +import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.models.Server; import spark.Request; import spark.Response; diff --git a/src/main/java/net/frozenorb/apiv3/weirdStuff/AuditLog.java b/src/main/java/net/frozenorb/apiv3/weirdStuff/AuditLog.java new file mode 100644 index 0000000..88dd3f2 --- /dev/null +++ b/src/main/java/net/frozenorb/apiv3/weirdStuff/AuditLog.java @@ -0,0 +1,20 @@ +package net.frozenorb.apiv3.weirdStuff; + +import lombok.experimental.UtilityClass; +import net.frozenorb.apiv3.APIv3; +import net.frozenorb.apiv3.models.AuditLogEntry; +import net.frozenorb.apiv3.models.User; +import org.bson.Document; + +@UtilityClass +public class AuditLog { + + public static void log(User performedBy, String performedFrom, String actionType, String description) { + log(performedBy, performedFrom, actionType, description, new Document()); + } + + public static void log(User performedBy, String performedFrom, String actionType, String description, Document actionData) { + APIv3.getDatastore().save(new AuditLogEntry(performedBy, performedFrom, actionType, description, actionData)); + } + +} \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/weirdStuff/ErrorUtils.java b/src/main/java/net/frozenorb/apiv3/weirdStuff/ErrorUtils.java new file mode 100644 index 0000000..da8d851 --- /dev/null +++ b/src/main/java/net/frozenorb/apiv3/weirdStuff/ErrorUtils.java @@ -0,0 +1,21 @@ +package net.frozenorb.apiv3.weirdStuff; + +import lombok.experimental.UtilityClass; +import org.bson.Document; + +@UtilityClass +public class ErrorUtils { + + public static Document notFound(String itemType, String id) { + return error(itemType + " with id " + id + " cannot be found."); + } + + public static Document unauthorized(String permission) { + return error("Unauthorized access. Permission \"" + permission + "\" required."); + } + + public static Document error(String reason) { + return new Document("success", false).append("reason", reason); + } + +} \ No newline at end of file diff --git a/src/main/java/net/frozenorb/apiv3/weirdStuff/ObjectIdTypeAdapter.java b/src/main/java/net/frozenorb/apiv3/weirdStuff/ObjectIdTypeAdapter.java index 8c6c547..ca1aec1 100644 --- a/src/main/java/net/frozenorb/apiv3/weirdStuff/ObjectIdTypeAdapter.java +++ b/src/main/java/net/frozenorb/apiv3/weirdStuff/ObjectIdTypeAdapter.java @@ -13,6 +13,9 @@ public final class ObjectIdTypeAdapter extends TypeAdapter { writer.value(write.toString()); } + // This is used with Gson, which is only used + // to serialize outgoing responses, thus we + // don't need to have a read method. public ObjectId read(JsonReader reader) { throw new IllegalArgumentException(); } diff --git a/src/main/java/net/frozenorb/apiv3/weirdStuff/Permissions.java b/src/main/java/net/frozenorb/apiv3/weirdStuff/Permissions.java new file mode 100644 index 0000000..269f290 --- /dev/null +++ b/src/main/java/net/frozenorb/apiv3/weirdStuff/Permissions.java @@ -0,0 +1,10 @@ +package net.frozenorb.apiv3.weirdStuff; + +import lombok.experimental.UtilityClass; + +@UtilityClass +public class Permissions { + + public static final String REMOVE_GRANT = "minehq.grant.remove"; // minehq.grant.remove.%RANK% + +} \ No newline at end of file