Whoa stuff
This commit is contained in:
parent
1b05b16fed
commit
6009522090
@ -24,10 +24,7 @@ import net.frozenorb.apiv3.routes.notificationTemplate.DELETENotificationTemplat
|
||||
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.punishments.*;
|
||||
import net.frozenorb.apiv3.routes.ranks.DELETERank;
|
||||
import net.frozenorb.apiv3.routes.ranks.GETRank;
|
||||
import net.frozenorb.apiv3.routes.ranks.GETRanks;
|
||||
@ -146,12 +143,14 @@ public final class APIv3 {
|
||||
private void setupHttp() {
|
||||
ipAddress(config.getProperty("http.address"));
|
||||
port(Integer.parseInt(config.getProperty("http.port")));
|
||||
// TODO: if threadPool == null use default value
|
||||
threadPool(Integer.parseInt(config.getProperty("http.workerThreads")));
|
||||
before(new ContentTypeFilter());
|
||||
before(new ActorAttributeFilter());
|
||||
before(new AuthorizationFilter());
|
||||
before(new MetricsBeforeFilter());
|
||||
after(new MetricsAfterFilter());
|
||||
after(new LoggingFilter());
|
||||
exception(Exception.class, new LoggingExceptionHandler());
|
||||
|
||||
// TODO: The commented out routes
|
||||
@ -200,13 +199,14 @@ public final class APIv3 {
|
||||
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/punishments", new GETUserPunishments(), 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/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);
|
||||
@ -214,6 +214,7 @@ 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/:serverGroup", new DELETEUserMeta(), gson::toJson);
|
||||
delete("/user/:id/punishment", new DELETEUserPunishment(), gson::toJson);
|
||||
|
||||
// There's no way to do a JSON 404 page w/o doing this :(
|
||||
get("/*", new NotFound(), gson::toJson);
|
||||
|
23
src/main/java/net/frozenorb/apiv3/filters/LoggingFilter.java
Normal file
23
src/main/java/net/frozenorb/apiv3/filters/LoggingFilter.java
Normal file
@ -0,0 +1,23 @@
|
||||
package net.frozenorb.apiv3.filters;
|
||||
|
||||
import com.codahale.metrics.Histogram;
|
||||
import com.codahale.metrics.MetricRegistry;
|
||||
import com.codahale.metrics.Timer;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import spark.Filter;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
|
||||
@Slf4j
|
||||
public final class LoggingFilter implements Filter {
|
||||
|
||||
public void handle(Request req, Response res) {
|
||||
if (req.url().toLowerCase().contains("password=")) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.info(req.requestMethod().toUpperCase() + " " + req.url());
|
||||
}
|
||||
|
||||
}
|
@ -8,15 +8,18 @@ import spark.Filter;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class MetricsAfterFilter implements Filter {
|
||||
|
||||
private Histogram responseLengthMetric = APIv3.getMetrics().histogram(MetricRegistry.name("apiv3", "http", "responseLength"));
|
||||
private Timer responseTimesMetric = APIv3.getMetrics().timer(MetricRegistry.name("apiv3", "http", "responseTimes"));
|
||||
|
||||
public void handle(Request req, Response res) {
|
||||
responseLengthMetric.update(req.contentLength());
|
||||
|
||||
Timer.Context timerMetric = req.attribute("timerMetric");
|
||||
timerMetric.stop();
|
||||
long started = req.attribute("requestStarted");
|
||||
responseTimesMetric.update(System.currentTimeMillis() - started, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
}
|
@ -9,10 +9,8 @@ import spark.Response;
|
||||
|
||||
public final class MetricsBeforeFilter implements Filter {
|
||||
|
||||
private Timer responseTimesMetric = APIv3.getMetrics().timer(MetricRegistry.name("apiv3", "http", "responseTimes"));
|
||||
|
||||
public void handle(Request req, Response res) {
|
||||
req.attribute("timerMetric", responseTimesMetric.time());
|
||||
req.attribute("requestStarted", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
}
|
@ -19,7 +19,7 @@ public final class Grant {
|
||||
@Getter @Id private String id;
|
||||
@Getter @Indexed private UUID target;
|
||||
@Getter private String reason;
|
||||
@Getter private Set<String> scopes;
|
||||
@Getter private Set<String> scopes = new HashSet<>(); // So on things w/o scopes we still load properly (Morphia drops empty sets)
|
||||
@Getter @Indexed private String rank;
|
||||
@Getter private Date expiresAt;
|
||||
|
||||
@ -42,8 +42,8 @@ public final class Grant {
|
||||
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.expiresAt = expiresAt;
|
||||
this.addedBy = addedBy == null ? null : addedBy.getId();
|
||||
this.addedAt = new Date();
|
||||
}
|
||||
|
||||
@ -63,7 +63,7 @@ public final class Grant {
|
||||
if (expiresAt == null) {
|
||||
return false; // Never expires
|
||||
} else {
|
||||
return expiresAt.after(new Date());
|
||||
return expiresAt.before(new Date());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,8 +41,8 @@ public final class Punishment {
|
||||
this.target = target.getId();
|
||||
this.reason = reason;
|
||||
this.type = type;
|
||||
this.expiresAt = (Date) expiresAt.clone();
|
||||
this.addedBy = addedBy.getId();
|
||||
this.expiresAt = expiresAt;
|
||||
this.addedBy = addedBy == null ? null : addedBy.getId();
|
||||
this.addedAt = new Date();
|
||||
this.actorName = actor.getName();
|
||||
this.actorType = actor.getType();
|
||||
@ -64,7 +64,7 @@ public final class Punishment {
|
||||
if (expiresAt == null) {
|
||||
return false; // Never expires
|
||||
} else {
|
||||
return expiresAt.after(new Date());
|
||||
return expiresAt.before(new Date());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ 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 org.mongodb.morphia.annotations.Property;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
@ -14,7 +15,8 @@ import java.util.*;
|
||||
public final class ServerGroup {
|
||||
|
||||
@Getter @Id private String id;
|
||||
@Getter private boolean isPublic;
|
||||
// We rename this to public, we just can't name it that because it's a Java identifier.
|
||||
@Getter @Property("public") 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<>();
|
||||
|
@ -30,7 +30,7 @@ public final class User {
|
||||
@Getter private String phoneNumber;
|
||||
@Getter private String lastSeenOn;
|
||||
@Getter private Date lastSeenAt;
|
||||
@Getter private Date firstSeen;
|
||||
@Getter private Date firstSeenAt;
|
||||
|
||||
public static User byId(String id) {
|
||||
try {
|
||||
@ -65,7 +65,7 @@ public final class User {
|
||||
this.phoneNumber = null;
|
||||
this.lastSeenOn = null;
|
||||
this.lastSeenAt = new Date();
|
||||
this.firstSeen = new Date();
|
||||
this.firstSeenAt = new Date();
|
||||
|
||||
aliases.put(lastUsername, new Date());
|
||||
}
|
||||
@ -157,7 +157,7 @@ public final class User {
|
||||
}
|
||||
|
||||
public Rank getHighestRank(ServerGroup serverGroup) {
|
||||
Rank highest = null;
|
||||
Rank highest = null;;
|
||||
|
||||
for (Grant grant : getGrants()) {
|
||||
if (!grant.isActive() || (serverGroup != null && !grant.appliesOn(serverGroup))) {
|
||||
@ -231,6 +231,7 @@ public final class User {
|
||||
|
||||
ServerGroup actorGroup = ServerGroup.byId(server.getGroup());
|
||||
Rank highestRank = getHighestRank(actorGroup);
|
||||
|
||||
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
|
||||
PermissionUtils.getDefaultPermissions(highestRank),
|
||||
actorGroup.calculatePermissions(highestRank)
|
||||
|
@ -23,12 +23,9 @@ public final class DELETEGrant implements Route {
|
||||
}
|
||||
|
||||
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");
|
||||
|
@ -31,8 +31,10 @@ public final class POSTUserGrant implements Route {
|
||||
}
|
||||
|
||||
Set<ServerGroup> scopes = new HashSet<>();
|
||||
String scopesUnparsed = req.queryParams("scopes");
|
||||
|
||||
for (String serverGroupId : req.queryParams("scopes").split(",")) {
|
||||
if (!scopesUnparsed.isEmpty()) {
|
||||
for (String serverGroupId : scopesUnparsed.split(",")) {
|
||||
ServerGroup serverGroup = ServerGroup.byId(serverGroupId);
|
||||
|
||||
if (serverGroup == null) {
|
||||
@ -41,6 +43,7 @@ public final class POSTUserGrant implements Route {
|
||||
|
||||
scopes.add(serverGroup);
|
||||
}
|
||||
}
|
||||
|
||||
Rank rank = Rank.byId(req.queryParams("rank"));
|
||||
|
||||
@ -48,20 +51,20 @@ public final class POSTUserGrant implements Route {
|
||||
return ErrorUtils.notFound("Rank", req.queryParams("rank"));
|
||||
}
|
||||
|
||||
Date expiresAt = new Date(Long.parseLong(req.queryParams("expiresAt")));
|
||||
Date expiresAt;
|
||||
|
||||
if (expiresAt.before(new Date())) {
|
||||
try {
|
||||
expiresAt = new Date(Long.parseLong(req.queryParams("expiresAt")));
|
||||
} catch (NumberFormatException ex) {
|
||||
expiresAt = null;
|
||||
}
|
||||
|
||||
if (expiresAt != null && expiresAt.before(new Date())) {
|
||||
return ErrorUtils.invalidInput("Expiration date cannot be in the past.");
|
||||
}
|
||||
|
||||
// We purposely don't do a null check, grants don't have to have a source.
|
||||
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);
|
||||
|
@ -23,12 +23,9 @@ public final class DELETEPunishment implements Route {
|
||||
}
|
||||
|
||||
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");
|
||||
|
@ -0,0 +1,21 @@
|
||||
package net.frozenorb.apiv3.routes.punishments;
|
||||
|
||||
import net.frozenorb.apiv3.models.User;
|
||||
import net.frozenorb.apiv3.utils.ErrorUtils;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
|
||||
public final class GETUserPunishments 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.getPunishments();
|
||||
}
|
||||
|
||||
}
|
@ -1,6 +1,10 @@
|
||||
package net.frozenorb.apiv3.routes.punishments;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
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;
|
||||
@ -27,20 +31,29 @@ public final class POSTUserPunish implements Route {
|
||||
}
|
||||
|
||||
Punishment.PunishmentType type = Punishment.PunishmentType.valueOf(req.queryParams("type"));
|
||||
Date expiresAt = new Date(Long.parseLong(req.queryParams("expiresAt")));
|
||||
|
||||
if (expiresAt.before(new Date())) {
|
||||
if (type != Punishment.PunishmentType.WARN) {
|
||||
for (Punishment punishment : target.getPunishments(ImmutableSet.of(type))) {
|
||||
if (punishment.isActive()) {
|
||||
return ErrorUtils.error("A punishment by " + User.byId(punishment.getAddedBy()).getLastUsername() + " already covers this user.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Date expiresAt;
|
||||
|
||||
try {
|
||||
expiresAt = new Date(Long.parseLong(req.queryParams("expiresAt")));
|
||||
} catch (NumberFormatException ex) {
|
||||
expiresAt = null;
|
||||
}
|
||||
|
||||
if (expiresAt != null && expiresAt.before(new Date())) {
|
||||
return ErrorUtils.invalidInput("Expiration date cannot be in the past.");
|
||||
}
|
||||
|
||||
// We purposely don't do a null check, grants don't have to have a source.
|
||||
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.");
|
||||
|
@ -0,0 +1,48 @@
|
||||
package net.frozenorb.apiv3.routes.users;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
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.utils.ErrorUtils;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
|
||||
public final class DELETEUserPunishment 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"));
|
||||
}
|
||||
|
||||
Punishment.PunishmentType type = Punishment.PunishmentType.valueOf(req.queryParams("type").toUpperCase());
|
||||
User removedBy = User.byId(req.queryParams("removedBy"));
|
||||
|
||||
if (removedBy == null) {
|
||||
return ErrorUtils.notFound("User", req.queryParams("removedBy"));
|
||||
}
|
||||
|
||||
String reason = req.queryParams("reason");
|
||||
|
||||
if (reason == null || reason.trim().isEmpty()) {
|
||||
return ErrorUtils.requiredInput("reason");
|
||||
}
|
||||
|
||||
for (Punishment punishment : target.getPunishments(ImmutableSet.of(type))) {
|
||||
if (punishment.isActive()) {
|
||||
punishment.delete(removedBy, reason);
|
||||
// TODO: Fix IP
|
||||
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of());
|
||||
return punishment;
|
||||
}
|
||||
}
|
||||
|
||||
return ErrorUtils.error("User provided has no active punishments");
|
||||
}
|
||||
|
||||
}
|
@ -24,18 +24,18 @@ public final class GETStaff implements Route {
|
||||
}
|
||||
});
|
||||
|
||||
Map<Rank, Set<User>> result = new HashMap<>();
|
||||
Map<String, 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<>());
|
||||
if (!result.containsKey(rank.getId())) {
|
||||
result.put(rank.getId(), new HashSet<>());
|
||||
}
|
||||
|
||||
result.get(rank).add(user);
|
||||
result.get(rank.getId()).add(user);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -23,7 +23,8 @@ public final class GETUserDetails implements Route {
|
||||
.put("ipLog", user.getIPLog())
|
||||
.put("punishments", user.getPunishments())
|
||||
.put("aliases", user.getAliases())
|
||||
.put("totpSetup", user.getTotpSecret() != null);
|
||||
.put("totpSetup", user.getTotpSecret() != null)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
@ -2,6 +2,7 @@ package net.frozenorb.apiv3.utils;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import net.frozenorb.apiv3.models.Rank;
|
||||
|
||||
import java.util.HashMap;
|
||||
@ -26,12 +27,10 @@ public class PermissionUtils {
|
||||
List<String> unconvertedPermissions = unconverted.get(rank.getId());
|
||||
|
||||
// If there's no permissions defined for this rank just skip it.
|
||||
if (unconvertedPermissions == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (unconvertedPermissions != null) {
|
||||
Map<String, Boolean> rankPermissions = convertToMap(unconvertedPermissions);
|
||||
mergePermissions(result, rankPermissions);
|
||||
result = mergePermissions(result, rankPermissions);
|
||||
}
|
||||
|
||||
if (upTo.getId().equals(rank.getId())) {
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user