More stuff

This commit is contained in:
Colin McDonald 2016-05-08 23:18:55 -04:00
parent 4b49c5bafe
commit d062ab5718
21 changed files with 230 additions and 72 deletions

View File

@ -1,15 +1,15 @@
general.releaseStage=production general.releaseStage=production
logging.level=info logging.level=info
mongo.address=ds055505.mongolab.com mongo.address=209.222.96.50
mongo.port=55505 mongo.port=27017
mongo.database=minehqapi mongo.database=minehqapi
mongo.username=test mongo.username=
mongo.password=test mongo.password=
redis.address=localhost redis.address=localhost
redis.port=6379 redis.port=6379
http.address=0.0.0.0 http.address=0.0.0.0
http.port=80 http.port=80
http.workerThreads=6 http.workerThreads=
twillio.accountSID=AC9e2f88c5690134d29a56f698de3cd740 twillio.accountSID=AC9e2f88c5690134d29a56f698de3cd740
twillio.authToken=982592505a171d3be6b0722f5ecacc0e twillio.authToken=982592505a171d3be6b0722f5ecacc0e
mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ

10
pom.xml
View File

@ -133,6 +133,16 @@
<artifactId>googleauth</artifactId> <artifactId>googleauth</artifactId>
<version>0.5.0</version> <version>0.5.0</version>
</dependency> </dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okio</groupId>
<artifactId>okio</artifactId>
<version>1.8.0</version>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>

View File

@ -1,9 +1,11 @@
package net.frozenorb.apiv3; package net.frozenorb.apiv3;
import com.bugsnag.Client; import com.bugsnag.Client;
import com.google.common.collect.ImmutableList;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.mongodb.MongoClient; import com.mongodb.MongoClient;
import com.mongodb.MongoCredential;
import com.mongodb.ServerAddress; import com.mongodb.ServerAddress;
import com.timgroup.statsd.NonBlockingStatsDClient; import com.timgroup.statsd.NonBlockingStatsDClient;
import com.timgroup.statsd.StatsDClient; import com.timgroup.statsd.StatsDClient;
@ -37,6 +39,7 @@ import net.frozenorb.apiv3.routes.users.*;
import net.frozenorb.apiv3.serialization.DateTypeAdapter; import net.frozenorb.apiv3.serialization.DateTypeAdapter;
import net.frozenorb.apiv3.serialization.FollowAnnotationExclusionStrategy; import net.frozenorb.apiv3.serialization.FollowAnnotationExclusionStrategy;
import net.frozenorb.apiv3.serialization.ObjectIdTypeAdapter; import net.frozenorb.apiv3.serialization.ObjectIdTypeAdapter;
import net.frozenorb.apiv3.unsorted.BugsnagSLF4jLogger;
import net.frozenorb.apiv3.unsorted.LoggingExceptionHandler; 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;
@ -77,6 +80,8 @@ public final class APIv3 {
setupMetrics(); setupMetrics();
setupBugsnag(); setupBugsnag();
setupHttp(); setupHttp();
LoggingFilter.setDebug(true);
} }
private void setupConfig() { private void setupConfig() {
@ -88,17 +93,21 @@ public final class APIv3 {
} }
private void setupDatabase() { private void setupDatabase() {
MongoClient mongoClient = new MongoClient(new ServerAddress( ImmutableList<MongoCredential> credentials = ImmutableList.of();
config.getProperty("mongo.address"),
Integer.parseInt(config.getProperty("mongo.port")))/*, if (!config.getProperty("mongo.username").isEmpty()) {
ImmutableList.of( credentials = ImmutableList.of(MongoCredential.createCredential(
MongoCredential.createCredential(
config.getProperty("mongo.username"), config.getProperty("mongo.username"),
config.getProperty("mongo.database"), config.getProperty("mongo.database"),
config.getProperty("mongo.password").toCharArray()) config.getProperty("mongo.password").toCharArray()
)*/); ));
}
// TODO: DISABLE CREDS IF NOT NEEDED MongoClient mongoClient = new MongoClient(new ServerAddress(
config.getProperty("mongo.address"),
Integer.parseInt(config.getProperty("mongo.port"))),
credentials
);
MorphiaLoggerFactory.reset(); MorphiaLoggerFactory.reset();
MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class); MorphiaLoggerFactory.registerLogger(SLF4JLoggerImplFactory.class);
@ -120,7 +129,7 @@ public final class APIv3 {
private void setupMetrics() { private void setupMetrics() {
statsD = new NonBlockingStatsDClient(null, "localhost", 8125); statsD = new NonBlockingStatsDClient(null, "localhost", 8125);
new Timer("Librato Post Task").scheduleAtFixedRate(new TimerTask() { new Timer("Metrics Task").scheduleAtFixedRate(new TimerTask() {
@Override @Override
public void run() { public void run() {
@ -135,15 +144,18 @@ public final class APIv3 {
Client bugsnag = new Client(config.getProperty("bugsnag.apiKey")); Client bugsnag = new Client(config.getProperty("bugsnag.apiKey"));
bugsnag.setReleaseStage(config.getProperty("general.releaseStage")); bugsnag.setReleaseStage(config.getProperty("general.releaseStage"));
bugsnag.setProjectPackages("net.frozenorb.apiv3"); bugsnag.setProjectPackages("net.frozenorb.apiv3");
bugsnag.setLogger(new BugsnagSLF4jLogger());
// TODO: Use .setLogger to use slf4j with this
} }
private void setupHttp() { private void setupHttp() {
ipAddress(config.getProperty("http.address")); ipAddress(config.getProperty("http.address"));
port(Integer.parseInt(config.getProperty("http.port"))); port(Integer.parseInt(config.getProperty("http.port")));
// TODO: if threadPool == null use default value String workerThreads = config.getProperty("http.workerThreads");
threadPool(Integer.parseInt(config.getProperty("http.workerThreads")));
if (!workerThreads.isEmpty()) {
threadPool(Integer.parseInt(workerThreads));
}
before(new ContentTypeFilter()); before(new ContentTypeFilter());
before(new ActorAttributeFilter()); before(new ActorAttributeFilter());
before(new AuthorizationFilter()); before(new AuthorizationFilter());

View File

@ -1,10 +1,11 @@
package net.frozenorb.apiv3.actors; package net.frozenorb.apiv3.actors;
import lombok.Getter;
import net.frozenorb.apiv3.models.Server; import net.frozenorb.apiv3.models.Server;
public final class ServerActor implements Actor { public final class ServerActor implements Actor {
private final Server server; @Getter private final Server server;
public ServerActor(Server server) { public ServerActor(Server server) {
this.server = server; this.server = server;

View File

@ -1,6 +1,7 @@
package net.frozenorb.apiv3.actors; package net.frozenorb.apiv3.actors;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
@ -10,7 +11,7 @@ public final class UserActor implements Actor {
private static final Set<String> authorizedUserGrants = ImmutableSet.copyOf(APIv3.getConfig().getProperty("auth.permittedUserRanks").split(",")); private static final Set<String> authorizedUserGrants = ImmutableSet.copyOf(APIv3.getConfig().getProperty("auth.permittedUserRanks").split(","));
private final User user; @Getter private final User user;
// We use Boolean here so we can have null = not calculated; // We use Boolean here so we can have null = not calculated;
private Boolean cachedAuthorized = null; private Boolean cachedAuthorized = null;

View File

@ -7,7 +7,7 @@ import spark.Response;
public final class ContentTypeFilter implements Filter { public final class ContentTypeFilter implements Filter {
public void handle(Request req, Response res) { public void handle(Request req, Response res) {
res.header("content-type", "application/json"); res.header("Content-Type", "application/json");
} }
} }

View File

@ -1,5 +1,7 @@
package net.frozenorb.apiv3.filters; package net.frozenorb.apiv3.filters;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import spark.Filter; import spark.Filter;
import spark.Request; import spark.Request;
@ -8,12 +10,14 @@ import spark.Response;
@Slf4j @Slf4j
public final class LoggingFilter implements Filter { public final class LoggingFilter implements Filter {
public void handle(Request req, Response res) { @Getter @Setter private static boolean debug = false;
if (req.url().toLowerCase().contains("password=")) {
return;
}
public void handle(Request req, Response res) {
if (debug) {
log.info(req.requestMethod().toUpperCase() + " " + req.url() + "\n" + res.body());
} else {
log.info(req.requestMethod().toUpperCase() + " " + req.url()); log.info(req.requestMethod().toUpperCase() + " " + req.url());
} }
}
} }

View File

@ -11,6 +11,7 @@ import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed; import org.mongodb.morphia.annotations.Indexed;
import java.util.Date; import java.util.Date;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
@Entity(value = "punishments", noClassnameStored = true) @Entity(value = "punishments", noClassnameStored = true)
@ -21,6 +22,7 @@ public final class Punishment {
@Getter private String reason; @Getter private String reason;
@Getter @Indexed private PunishmentType type; // Type is indexed for the rank dump @Getter @Indexed private PunishmentType type; // Type is indexed for the rank dump
@Getter private Date expiresAt; @Getter private Date expiresAt;
@Getter private Map<String, Object> meta;
@Getter private UUID addedBy; @Getter private UUID addedBy;
@Getter @Indexed private Date addedAt; @Getter @Indexed private Date addedAt;
@ -37,7 +39,7 @@ public final class Punishment {
public Punishment() {} // For Morphia public Punishment() {} // For Morphia
public Punishment(User target, String reason, PunishmentType type, Date expiresAt, User addedBy, Actor actor) { public Punishment(User target, String reason, PunishmentType type, Date expiresAt, User addedBy, Actor actor, Map<String, Object> meta) {
this.id = new ObjectId().toString(); this.id = new ObjectId().toString();
this.target = target.getId(); this.target = target.getId();
this.reason = reason; this.reason = reason;
@ -47,6 +49,7 @@ public final class Punishment {
this.addedAt = new Date(); this.addedAt = new Date();
this.actorName = actor.getName(); this.actorName = actor.getName();
this.actorType = actor.getType(); this.actorType = actor.getType();
this.meta = meta;
} }
public void delete(User removedBy, String reason) { public void delete(User removedBy, String reason) {

View File

@ -1,16 +1,23 @@
package net.frozenorb.apiv3.models; package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableList;
import lombok.Getter; import lombok.Getter;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id; import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed; import org.mongodb.morphia.annotations.Indexed;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Entity(value = "ranks", noClassnameStored = true) @Entity(value = "ranks", noClassnameStored = true)
public final class Rank { public final class Rank {
private static Map<String, Rank> rankCache = null;
private static long rankCacheUpdated = 0;
@Getter @Id private String id; @Getter @Id private String id;
@Getter private int weight; @Getter private int weight;
@Getter private String displayName; @Getter private String displayName;
@ -19,11 +26,12 @@ public final class Rank {
@Getter @Indexed private boolean staffRank; @Getter @Indexed private boolean staffRank;
public static Rank byId(String id) { public static Rank byId(String id) {
return APIv3.getDatastore().createQuery(Rank.class).field("id").equal(id).get(); updateCacheIfNeeded();
return rankCache.get(id);
} }
public static List<Rank> values() { public static List<Rank> values() {
return APIv3.getDatastore().createQuery(Rank.class).order("weight").asList(); return ImmutableList.copyOf(rankCache.values());
} }
public Rank() {} // For Morphia public Rank() {} // For Morphia
@ -41,4 +49,17 @@ public final class Rank {
APIv3.getDatastore().delete(this); APIv3.getDatastore().delete(this);
} }
private static void updateCacheIfNeeded() {
if (rankCache == null || (System.currentTimeMillis() - rankCacheUpdated) > TimeUnit.MINUTES.toMillis(1)) {
Map<String, Rank> working = new HashMap<>();
for (Rank rank : APIv3.getDatastore().createQuery(Rank.class).order("weight").asList()) {
working.put(rank.getId(), rank);
}
rankCache = working;
rankCacheUpdated = System.currentTimeMillis();
}
}
} }

View File

@ -7,6 +7,7 @@ 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 net.frozenorb.apiv3.serialization.ExcludeFromReplies;
import net.frozenorb.apiv3.utils.MojangUtils;
import net.frozenorb.apiv3.utils.PermissionUtils; import net.frozenorb.apiv3.utils.PermissionUtils;
import org.bson.Document; import org.bson.Document;
import org.mindrot.jbcrypt.BCrypt; import org.mindrot.jbcrypt.BCrypt;
@ -57,7 +58,7 @@ public final class User {
public User(UUID id, String lastUsername) { public User(UUID id, String lastUsername) {
this.id = id; this.id = id;
this.lastUsername = lastUsername; this.lastUsername = ""; // Intentional, so updateUsername actually does something.
this.aliases = new HashMap<>(); this.aliases = new HashMap<>();
this.totpSecret = null; this.totpSecret = null;
this.password = null; this.password = null;
@ -67,7 +68,7 @@ public final class User {
this.lastSeenAt = new Date(); this.lastSeenAt = new Date();
this.firstSeenAt = new Date(); this.firstSeenAt = new Date();
aliases.put(lastUsername, new Date()); updateUsername(lastUsername);
} }
public boolean hasPermissionScoped(String permission, ServerGroup scope) { public boolean hasPermissionScoped(String permission, ServerGroup scope) {
@ -138,9 +139,23 @@ public final class User {
} }
} }
public void seenOnServer(String username, Server server) { public void seenOnServer(Server server) {
this.lastSeenOn = server.getId(); this.lastSeenOn = server.getId();
this.lastSeenAt = new Date(); this.lastSeenAt = new Date();
}
public void updateUsername(String username) {
if (!username.equals(lastUsername)) {
this.lastUsername = username;
User withNewUsername;
while ((withNewUsername = User.byLastUsername(username)) != null) {
String newUsername = MojangUtils.getName(withNewUsername.getId());
withNewUsername.updateUsername(newUsername);
}
}
this.aliases.put(username, new Date()); this.aliases.put(username, new Date());
} }
@ -157,7 +172,7 @@ public final class User {
} }
public Rank getHighestRank(ServerGroup serverGroup) { public Rank getHighestRank(ServerGroup serverGroup) {
Rank highest = null;; Rank highest = null;
for (Grant grant : getGrants()) { for (Grant grant : getGrants()) {
if (!grant.isActive() || (serverGroup != null && !grant.appliesOn(serverGroup))) { if (!grant.isActive() || (serverGroup != null && !grant.appliesOn(serverGroup))) {
@ -205,51 +220,43 @@ public final class User {
} }
public Map<String, Object> getLoginInfo(Server server) { public Map<String, Object> getLoginInfo(Server server) {
Punishment activeMute = null;
String accessDenialReason = null; String accessDenialReason = null;
for (Punishment punishment : getPunishments(ImmutableSet.of( for (Punishment punishment : getPunishments(ImmutableSet.of(
Punishment.PunishmentType.BLACKLIST, Punishment.PunishmentType.BLACKLIST,
Punishment.PunishmentType.BAN Punishment.PunishmentType.BAN,
Punishment.PunishmentType.MUTE
))) { ))) {
if (!punishment.isActive()) { if (!punishment.isActive()) {
continue; continue;
} }
if (punishment.getType() == Punishment.PunishmentType.MUTE) {
activeMute = punishment;
} else {
accessDenialReason = punishment.getAccessDenialReason(); accessDenialReason = punishment.getAccessDenialReason();
} }
Punishment mute = null;
for (Punishment punishment : getPunishments(ImmutableSet.of(Punishment.PunishmentType.MUTE))) {
if (!punishment.isActive()) {
continue;
}
mute = punishment;
} }
ServerGroup actorGroup = ServerGroup.byId(server.getGroup()); ServerGroup actorGroup = ServerGroup.byId(server.getGroup());
Rank highestRank = getHighestRank(actorGroup); Rank highestRank = getHighestRank(actorGroup);
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions( // Generics are weird, yes we have to do this.
PermissionUtils.getDefaultPermissions(highestRank), ImmutableMap.Builder<String, Object> result = ImmutableMap.<String, Object>builder()
actorGroup.calculatePermissions(highestRank) .put("user", this)
); .put("access", ImmutableMap.of(
Map<String, Object> loginInfo = Maps.newHashMap();
loginInfo.put("user", this);
loginInfo.put("access", ImmutableMap.of(
"allowed", accessDenialReason == null, "allowed", accessDenialReason == null,
"message", accessDenialReason == null ? "Public server" : accessDenialReason "message", accessDenialReason == null ? "Public server" : accessDenialReason
)); ))
loginInfo.put("rank", highestRank.getId()); .put("rank", highestRank.getId())
loginInfo.put("permissions", scopedPermissions); .put("totpSetup", getTotpSecret() != null);
loginInfo.put("totpSetup", getTotpSecret() != null);
if (mute != null) { if (activeMute != null) {
loginInfo.put("mute", mute); result.put("mute", activeMute);
} }
return loginInfo; return result.build();
} }
} }

View File

@ -2,6 +2,7 @@ package net.frozenorb.apiv3.routes.announcements;
import net.frozenorb.apiv3.actors.Actor; import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType; import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.actors.ServerActor;
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 net.frozenorb.apiv3.utils.ErrorUtils;
@ -18,7 +19,7 @@ public final class GETAnnouncements implements Route {
return ErrorUtils.serverOnly(); return ErrorUtils.serverOnly();
} }
Server sender = Server.byId(actor.getName()); Server sender = ((ServerActor) req.attribute("actor")).getServer();
ServerGroup senderGroup = ServerGroup.byId(sender.getGroup()); ServerGroup senderGroup = ServerGroup.byId(sender.getGroup());
return senderGroup.getAnnouncements(); return senderGroup.getAnnouncements();

View File

@ -16,7 +16,7 @@ public final class GETAuditLog implements Route {
return APIv3.getDatastore().createQuery(AuditLogEntry.class).order("performedAt").limit(limit).offset(offset).asList(); return APIv3.getDatastore().createQuery(AuditLogEntry.class).order("performedAt").limit(limit).offset(offset).asList();
} catch (NumberFormatException ex) { } catch (NumberFormatException ex) {
return ErrorUtils.invalidInput(";imit and offset must be numerical inputs."); return ErrorUtils.invalidInput("limit and offset must be numerical inputs.");
} }
} }

View File

@ -34,7 +34,6 @@ public final class DELETEGrant implements Route {
} }
grant.delete(removedBy, reason); grant.delete(removedBy, reason);
// TODO: Fix IP
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_GRANT, ImmutableMap.of()); AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_GRANT, ImmutableMap.of());
return grant; return grant;
} }

View File

@ -34,7 +34,6 @@ public final class DELETEPunishment implements Route {
} }
punishment.delete(removedBy, reason); punishment.delete(removedBy, reason);
// TODO: Fix IP
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of()); AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of());
return punishment; return punishment;
} }

View File

@ -7,11 +7,13 @@ import net.frozenorb.apiv3.models.Punishment;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Permissions; import net.frozenorb.apiv3.unsorted.Permissions;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
import spark.Request; import spark.Request;
import spark.Response; import spark.Response;
import spark.Route; import spark.Route;
import java.util.Date; import java.util.Date;
import java.util.Map;
public final class POSTUserPunish implements Route { public final class POSTUserPunish implements Route {
@ -50,6 +52,12 @@ public final class POSTUserPunish implements Route {
return ErrorUtils.invalidInput("Expiration date cannot be in the past."); return ErrorUtils.invalidInput("Expiration date cannot be in the past.");
} }
Map<String, Object> meta = Document.parse(req.body());
if (meta == null) {
return ErrorUtils.requiredInput("request body meta");
}
// We purposely don't do a null check, grants don't have to have a source. // We purposely don't do a null check, grants don't have to have a source.
User addedBy = User.byId(req.queryParams("addedBy")); User addedBy = User.byId(req.queryParams("addedBy"));
@ -57,12 +65,13 @@ public final class POSTUserPunish implements Route {
return ErrorUtils.error(target.getLastSeenOn() + " is protected from punishments."); return ErrorUtils.error(target.getLastSeenOn() + " is protected from punishments.");
} }
Punishment punishment = new Punishment(target, reason, type, expiresAt, addedBy, req.attribute("actor")); Punishment punishment = new Punishment(target, reason, type, expiresAt, addedBy, req.attribute("actor"), meta);
String accessDenialReason = punishment.getAccessDenialReason();
APIv3.getDatastore().save(punishment); APIv3.getDatastore().save(punishment);
return ImmutableMap.of( return ImmutableMap.of(
"punishment", punishment, "punishment", punishment,
"accessDenialReason", punishment.getAccessDenialReason() == null ? "" : punishment.getAccessDenialReason() "accessDenialReason", accessDenialReason == null ? "" : accessDenialReason
); );
} }

View File

@ -1,12 +1,16 @@
package net.frozenorb.apiv3.routes.servers; package net.frozenorb.apiv3.routes.servers;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor; import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.actors.ActorType; import net.frozenorb.apiv3.actors.ActorType;
import net.frozenorb.apiv3.models.Rank;
import net.frozenorb.apiv3.models.Server; import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.ServerGroup;
import net.frozenorb.apiv3.models.User; import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils; import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.PermissionUtils;
import org.bson.Document; import org.bson.Document;
import spark.Request; import spark.Request;
import spark.Response; import spark.Response;
@ -14,6 +18,7 @@ import spark.Route;
import java.util.*; import java.util.*;
@Slf4j
public final class POSTServerHeartbeat implements Route { public final class POSTServerHeartbeat implements Route {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@ -25,6 +30,7 @@ public final class POSTServerHeartbeat implements Route {
} }
Server actorServer = Server.byId(actor.getName()); Server actorServer = Server.byId(actor.getName());
ServerGroup actorServerGroup = ServerGroup.byId(actorServer.getGroup());
Document reqJson = Document.parse(req.body()); Document reqJson = Document.parse(req.body());
Set<UUID> onlinePlayers = new HashSet<>(); Set<UUID> onlinePlayers = new HashSet<>();
Map<String, Object> playersResponse = new HashMap<>(); Map<String, Object> playersResponse = new HashMap<>();
@ -39,7 +45,7 @@ public final class POSTServerHeartbeat implements Route {
user = new User(UUID.fromString(playerJson.getString("uuid")), username); user = new User(UUID.fromString(playerJson.getString("uuid")), username);
} }
user.seenOnServer(username, actorServer); user.seenOnServer(actorServer);
APIv3.getDatastore().save(user); APIv3.getDatastore().save(user);
onlinePlayers.add(user.getId()); onlinePlayers.add(user.getId());
@ -58,17 +64,29 @@ public final class POSTServerHeartbeat implements Route {
case "metrics": case "metrics":
break; break;
default: default:
System.err.println("Recieved event with unknown type " + type + "."); log.warn("Recieved event with unknown type " + type + ".");
} }
} }
Map<String, Map<String, Boolean>> permissions = new HashMap<>();
for (Rank rank : Rank.values()) {
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
PermissionUtils.getDefaultPermissions(rank),
actorServerGroup.calculatePermissions(rank)
);
permissions.put(rank.getId(), scopedPermissions);
}
actorServer.setPlayers(onlinePlayers); actorServer.setPlayers(onlinePlayers);
actorServer.setLastTps(reqJson.getDouble("lastTps")); actorServer.setLastTps(reqJson.getDouble("lastTps"));
actorServer.setLastUpdate(new Date()); actorServer.setLastUpdate(new Date());
APIv3.getDatastore().save(actorServer); APIv3.getDatastore().save(actorServer);
return ImmutableMap.of( return ImmutableMap.of(
"players", playersResponse "players", playersResponse,
"permissions", permissions
); );
} }

View File

@ -36,7 +36,6 @@ public final class DELETEUserPunishment implements Route {
for (Punishment punishment : target.getPunishments(ImmutableSet.of(type))) { for (Punishment punishment : target.getPunishments(ImmutableSet.of(type))) {
if (punishment.isActive()) { if (punishment.isActive()) {
punishment.delete(removedBy, reason); punishment.delete(removedBy, reason);
// TODO: Fix IP
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of()); AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of());
return punishment; return punishment;
} }

View File

@ -37,6 +37,8 @@ public final class POSTUserLogin implements Route {
Server actorServer = Server.byId(actor.getName()); Server actorServer = Server.byId(actor.getName());
user.getIPLogEntry(userIp).used(); user.getIPLogEntry(userIp).used();
user.updateUsername(username);
APIv3.getDatastore().save(user);
return user.getLoginInfo(actorServer); return user.getLoginInfo(actorServer);
} }

View File

@ -10,7 +10,11 @@ import java.util.Date;
public final class DateTypeAdapter extends TypeAdapter<Date> { public final class DateTypeAdapter extends TypeAdapter<Date> {
public void write(JsonWriter writer, Date write) throws IOException { public void write(JsonWriter writer, Date write) throws IOException {
writer.value(write == null ? -1 : write.getTime()); if (write == null) {
writer.value(-1);
} else {
writer.value(write.getTime());
}
} }
// This is used with Gson, which is only used // This is used with Gson, which is only used

View File

@ -0,0 +1,29 @@
package net.frozenorb.apiv3.unsorted;
import com.bugsnag.Logger;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class BugsnagSLF4jLogger extends Logger {
public void debug(String message) {
log.debug(message);
}
public void info(String message) {
log.info(message);
}
public void warn(String message) {
log.warn(message);
}
public void warn(String message, Throwable ex) {
log.warn(message, ex);
}
public void warn(Throwable ex) {
log.warn("error in bugsnag", ex);
}
}

View File

@ -0,0 +1,39 @@
package net.frozenorb.apiv3.utils;
import lombok.experimental.UtilityClass;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import org.bson.Document;
import java.io.IOException;
import java.util.UUID;
@UtilityClass
public class MojangUtils {
private static OkHttpClient okHttpClient = new OkHttpClient();
public static String getName(UUID id) {
Request.Builder builder = new Request.Builder();
builder.get();
builder.url("https://sessionserver.mojang.com/session/minecraft/profile/" + id.toString().replace("-", ""));
try {
Response response = okHttpClient.newCall(builder.build()).execute();
Document resJson = Document.parse(response.body().string());
String name = resJson.getString("name");
if (name == null) {
throw new RuntimeException("Hit Mojang API rate limit");
}
return name;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}