Push code for griffin

This commit is contained in:
Colin McDonald 2016-05-02 19:34:30 -04:00
parent c3efe78ce4
commit 799a8de386
27 changed files with 184 additions and 57 deletions

View File

@ -87,6 +87,11 @@
<artifactId>morphia-logging-slf4j</artifactId>
<version>1.1.0</version>
</dependency>
<dependency>
<groupId>com.warrenstrange</groupId>
<artifactId>googleauth</artifactId>
<version>0.5.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>

View File

@ -10,7 +10,6 @@ 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;
@ -45,7 +44,6 @@ 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;
@ -57,7 +55,7 @@ public final class APIv3 {
@Getter private static Datastore datastore;
@Getter private static Properties config = new Properties();
private final Gson gson = new GsonBuilder()
@Getter private static final Gson gson = new GsonBuilder()
.registerTypeAdapter(ObjectId.class, new ObjectIdTypeAdapter())
.setExclusionStrategies(new FollowAnnotationExclusionStrategy())
.create();
@ -152,12 +150,14 @@ public final class APIv3 {
get("/user/:id/meta/:serverGroup", new GETUserMeta(), gson::toJson);
get("/user/:id/grants", new GETUserGrants(), gson::toJson);
get("/user/:id/ipLog", new GETUserIPLog(), gson::toJson);
get("/user/:id/verifyTOTP", new GETUserVerifyTOTP(), gson::toJson);
get("/user/:id", new GETUser(), gson::toJson);
post("/user/:id:/grant", new POSTUserGrant(), gson::toJson);
post("/user/:id:/punish", new POSTUserPunish(), gson::toJson);
post("/user/:id/loginInfo", new POSTUserLoginInfo(), gson::toJson);
post("/user/:id/notify", new POSTUserNotify(), gson::toJson);
post("/user/:id/register", new POSTUserRegister(), gson::toJson);
post("/user/:id/setupTOTP", new POSTUserSetupTOTP(), gson::toJson);
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);

View File

@ -3,12 +3,10 @@ package net.frozenorb.apiv3.auditLog;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.auditLog.actions.CreatePunishmentAction;
import net.frozenorb.apiv3.models.AuditLogEntry;
import net.frozenorb.apiv3.models.User;
import org.bson.Document;
import java.util.HashMap;
import java.util.Map;
@UtilityClass
@ -18,7 +16,7 @@ public class AuditLog {
log(performedBy, performedByIp, actor, actionType, new Document());
}
public static void log(User performedBy, String performedByIp, Actor actor, AuditLogActionType actionType, Document actionData) {
public static void log(User performedBy, String performedByIp, Actor actor, AuditLogActionType actionType, Map<String, Object> actionData) {
APIv3.getDatastore().save(new AuditLogEntry(performedBy, performedByIp, actor, actionType, actionData));
}

View File

@ -1,11 +1,18 @@
package net.frozenorb.apiv3.auditLog;
import lombok.Getter;
import net.frozenorb.apiv3.models.AuditLogEntry;
public enum AuditLogActionType {
CREATE_PUNISHMENT {
DELETE_PUNISHMENT {
@Override
public void revert(AuditLogEntry entry) {
}
},
DELETE_GRANT {
@Override
public void revert(AuditLogEntry entry) {

View File

@ -9,8 +9,7 @@ import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Filter;
import spark.Request;
import spark.Response;
import static spark.Spark.halt;
import spark.Spark;
public final class ActorAttributeFilter implements Filter {
@ -40,7 +39,7 @@ public final class ActorAttributeFilter implements Filter {
}
}
halt(401, ErrorUtils.error("Failed to authorize.").toJson());
Spark.halt(401, APIv3.getGson().toJson(ErrorUtils.error("Failed to authorize.")));
return null;
}
@ -61,7 +60,7 @@ public final class ActorAttributeFilter implements Filter {
Server server = Server.byId(split[1]);
if (server == null) {
halt(401, ErrorUtils.notFound("Server", split[1]).toJson());
Spark.halt(401, APIv3.getGson().toJson(ErrorUtils.notFound("Server", split[1])));
}
String givenKey = split[2];
@ -73,7 +72,7 @@ public final class ActorAttributeFilter implements Filter {
}
}
halt(401, ErrorUtils.error("Failed to authorize.").toJson());
Spark.halt(401, APIv3.getGson().toJson(ErrorUtils.error("Failed to authorize.")));
return null;
}

View File

@ -1,5 +1,6 @@
package net.frozenorb.apiv3.filters;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Filter;
@ -13,7 +14,7 @@ public final class AuthorizationFilter implements Filter {
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());
Spark.halt(APIv3.getGson().toJson(ErrorUtils.error("Unauthorized access: Please authenticate as either a server, the website, or an authorized user. You're currently authorized as " + actor.getName())));
}
}

View File

@ -4,10 +4,10 @@ import com.google.common.collect.ImmutableMap;
import lombok.Getter;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.Date;
import java.util.Map;
@ -17,9 +17,9 @@ import java.util.UUID;
public final class AuditLogEntry {
@Getter @Id private String id;
@Getter private UUID performedBy;
@Getter @Indexed private UUID performedBy;
@Getter private String performedByIp;
@Getter private Date performedAt;
@Getter @Indexed private Date performedAt;
@Getter private String actorName;
@Getter private Actor.Type actorType;
@Getter private AuditLogActionType actionType;

View File

@ -6,6 +6,7 @@ import net.frozenorb.apiv3.APIv3;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.Date;
import java.util.HashSet;
@ -16,14 +17,14 @@ import java.util.UUID;
public final class Grant {
@Getter @Id private String id;
@Getter private UUID target;
@Getter @Indexed private UUID target;
@Getter private String reason;
@Getter private Set<String> scopes;
@Getter private String rank;
@Getter @Indexed private String rank;
@Getter private Date expiresAt;
@Getter private UUID addedBy;
@Getter private Date addedAt;
@Getter @Indexed private Date addedAt;
@Getter private UUID removedBy;
@Getter private Date removedAt;

View File

@ -5,6 +5,7 @@ import net.frozenorb.apiv3.APIv3;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.Date;
import java.util.UUID;
@ -13,8 +14,8 @@ import java.util.UUID;
public final class IPLogEntry {
@Getter @Id private String id;
@Getter private UUID user;
@Getter private String ip;
@Getter @Indexed private UUID user;
@Getter @Indexed private String ip;
@Getter private Date firstSeen;
@Getter private Date lastSeen;
@Getter private int uses;

View File

@ -5,6 +5,7 @@ import net.frozenorb.apiv3.APIv3;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.Date;
import java.util.UUID;
@ -13,14 +14,14 @@ import java.util.UUID;
public final class Punishment {
@Getter @Id private String id;
@Getter private UUID target;
@Getter @Indexed private UUID target;
@Getter private String reason;
@Getter private PunishmentType type;
@Getter @Indexed private PunishmentType type; // Type is indexed for the rank dump
@Getter private Date expiresAt;
@Getter private UUID addedBy;
@Getter private Date addedAt;
@Getter private String addedOn;
@Getter @Indexed private Date addedAt;
@Getter private String addedOn; // TODO: Make this store actor like the audit log?
@Getter private UUID removedBy;
@Getter private Date removedAt;

View File

@ -4,6 +4,7 @@ import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.List;
@ -15,7 +16,7 @@ public final class Rank {
@Getter private String displayName;
@Getter private String gameColor;
@Getter private String websiteColor;
@Getter private boolean staffRank;
@Getter @Indexed private boolean staffRank;
public static Rank byId(String id) {
return APIv3.getDatastore().createQuery(Rank.class).field("id").equal(id).get();

View File

@ -6,6 +6,7 @@ import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.*;
@ -16,7 +17,7 @@ public final class Server {
@Getter private String bungeeId;
@Getter private String displayName;
@Getter @ExcludeFromReplies String apiKey;
@Getter private String group;
@Getter @Indexed private String group;
@Getter private String ip;
@Getter @Setter private Date lastUpdate;
@Getter @Setter private double lastTps;

View File

@ -11,6 +11,7 @@ import org.bson.Document;
import org.mindrot.jbcrypt.BCrypt;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.*;
@ -18,10 +19,10 @@ import java.util.*;
public final class User {
@Getter @Id private UUID id;
@Getter private String lastUsername;
@Getter @Indexed private String lastUsername;
@Getter @ExcludeFromReplies private Map<String, Date> aliases;
@Getter @ExcludeFromReplies private String otpCode;
@Getter @ExcludeFromReplies @Setter private String emailToken;
@Getter @Setter @ExcludeFromReplies private String totpSecret;
@Getter @Indexed @ExcludeFromReplies @Setter private String emailToken;
@Getter @ExcludeFromReplies @Setter private Date emailTokenSet;
@Getter @ExcludeFromReplies private String password;
@Getter @Setter private String email;
@ -57,7 +58,7 @@ public final class User {
this.id = id;
this.lastUsername = lastUsername;
this.aliases = new HashMap<>();
this.otpCode = null;
this.totpSecret = null;
this.password = null;
this.email = null;
this.phoneNumber = -1;

View File

@ -7,6 +7,7 @@ import org.bson.Document;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
import org.mongodb.morphia.annotations.Indexed;
import java.util.UUID;
@ -14,8 +15,8 @@ import java.util.UUID;
public final class UserMetaEntry {
@Getter @Id private String id;
@Getter private UUID user;
@Getter private String serverGroup;
@Getter @Indexed private UUID user;
@Getter @Indexed private String serverGroup;
@Getter @Setter private Document data;
public UserMetaEntry() {} // For Morphia

View File

@ -2,7 +2,6 @@ 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;

View File

@ -1,8 +1,6 @@
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;
@ -12,7 +10,7 @@ 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());
Spark.halt(404, APIv3.getGson().toJson(ErrorUtils.notFound("Route", req.url())));
return null;
}

View File

@ -1,8 +1,9 @@
package net.frozenorb.apiv3.routes.grants;
import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
import net.frozenorb.apiv3.models.Grant;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.auditLog.AuditLog;
import net.frozenorb.apiv3.unsorted.Permissions;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
@ -33,7 +34,8 @@ public final class DELETEGrant implements Route {
String reason = req.queryParams("removalReason");
grant.delete(removedBy, reason);
AuditLog.log(removedBy, req.attribute("actor"), "grant.remove", new Document("grantId", grant.getId()));
// TODO: Fix IP
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_GRANT, new Document("grantId", grant.getId()));
return grant;
}

View File

@ -1,7 +1,6 @@
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;

View File

@ -1,8 +1,9 @@
package net.frozenorb.apiv3.routes.punishments;
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.auditLog.AuditLog;
import net.frozenorb.apiv3.unsorted.Permissions;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
@ -33,7 +34,8 @@ public final class DELETEPunishment implements Route {
String reason = req.queryParams("removalReason");
punishment.delete(removedBy, reason);
AuditLog.log(removedBy, req.attribute("actor"), "punishment.remove", new Document("punishmentId", punishment.getId()));
// TODO: Fix IP
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_PUNISHMENT, new Document("punishmentId", punishment.getId()));
return punishment;
}

View File

@ -0,0 +1,31 @@
package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.TOTPUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class GETUserVerifyTOTP implements Route {
public Object handle(Request req, Response res) {
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
if (user.getTotpSecret() == null) {
return ErrorUtils.invalidInput("User provided does not have TOTP code set.");
}
int providedCode = Integer.parseInt(req.queryParams("code"));
return ImmutableMap.of(
"verified", TOTPUtils.authorizeUser(user, providedCode)
);
}
}

View File

@ -1,10 +1,10 @@
package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
import spark.Request;
import spark.Response;
import spark.Route;
@ -51,7 +51,9 @@ public final class POSTUserConfirmRegister implements Route {
user.setPassword(password.toCharArray());
APIv3.getDatastore().save(user);
return new Document("success", true).append("message", "User confirmed");
return ImmutableMap.of(
"success", true
);
}
}

View File

@ -1,10 +1,10 @@
package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.models.NotificationTemplate;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Notification;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
import spark.Request;
import spark.Response;
import spark.Route;
@ -46,7 +46,9 @@ public final class POSTUserNotify implements Route {
Notification notification = new Notification(template, subjectReplacements, bodyReplacements);
notification.sendAsEmail(user.getEmail());
return new Document("success", true);
return ImmutableMap.of(
"success", true
);
} catch (Exception ex) {
ex.printStackTrace();
return ErrorUtils.error("Failed to send notification");

View File

@ -6,7 +6,6 @@ import net.frozenorb.apiv3.models.NotificationTemplate;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Notification;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
import spark.Request;
import spark.Response;
import spark.Route;
@ -59,7 +58,9 @@ public final class POSTUserRegister implements Route {
try {
notification.sendAsEmail(user.getEmail());
return new Document("success", true).append("message", "User registered");
return ImmutableMap.of(
"success", true
);
} catch (Exception ex) {
ex.printStackTrace();
return ErrorUtils.error("Failed to send confirmation email. Please contact a MineHQ staff member.");

View File

@ -0,0 +1,36 @@
package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableMap;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.utils.ErrorUtils;
import net.frozenorb.apiv3.utils.TOTPUtils;
import spark.Request;
import spark.Response;
import spark.Route;
public final class POSTUserSetupTOTP implements Route {
public Object handle(Request req, Response res) {
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
if (user.getTotpSecret() != null) {
return ErrorUtils.invalidInput("User provided already has TOTP code set.");
}
GoogleAuthenticatorKey generated = TOTPUtils.generateTOTPKey();
user.setTotpSecret(generated.getKey());
APIv3.getDatastore().save(user);
return ImmutableMap.of(
"qrCode", TOTPUtils.getQRCodeURL(user, generated)
);
}
}

View File

@ -1,6 +1,7 @@
package net.frozenorb.apiv3.unsorted;
import lombok.extern.slf4j.Slf4j;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.types.ObjectId;
import spark.ExceptionHandler;
@ -16,7 +17,7 @@ public final class LoggingExceptionHandler implements ExceptionHandler {
log.error(code + ":");
ex.printStackTrace();
res.body(ErrorUtils.error("An unknown error has occurred. Please contact a developer with the code \"" + code + "\".").toJson());
res.body(APIv3.getGson().toJson(ErrorUtils.error("An unknown error has occurred. Please contact a developer with the code \"" + code + "\".")));
}
}

View File

@ -1,29 +1,34 @@
package net.frozenorb.apiv3.utils;
import com.google.common.collect.ImmutableMap;
import lombok.experimental.UtilityClass;
import org.bson.Document;
import java.util.Map;
@UtilityClass
public class ErrorUtils {
public static Document serverOnly() {
public static Map<String, Object> serverOnly() {
return error("This action can only be performed when requested by a server.");
}
public static Document notFound(String itemType, String id) {
public static Map<String, Object> notFound(String itemType, String id) {
return error("Not found: " + itemType + " with id " + id + " cannot be found.");
}
public static Document unauthorized(String permission) {
public static Map<String, Object> unauthorized(String permission) {
return error("Unauthorized access: Permission \"" + permission + "\" required.");
}
public static Document invalidInput(String message) {
public static Map<String, Object> invalidInput(String message) {
return error("Invalid input: " + message);
}
public static Document error(String message) {
return new Document("success", false).append("message", message);
public static Map<String, Object> error(String message) {
return ImmutableMap.of(
"success", false,
"message", message
);
}
}

View File

@ -0,0 +1,32 @@
package net.frozenorb.apiv3.utils;
import com.google.common.collect.ImmutableList;
import com.warrenstrange.googleauth.GoogleAuthenticator;
import com.warrenstrange.googleauth.GoogleAuthenticatorKey;
import com.warrenstrange.googleauth.GoogleAuthenticatorQRGenerator;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.models.User;
import org.apache.http.client.utils.URIBuilder;
@UtilityClass
public class TOTPUtils {
private static GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
public static GoogleAuthenticatorKey generateTOTPKey() {
return googleAuthenticator.createCredentials();
}
public static boolean authorizeUser(User user, int code) {
return googleAuthenticator.authorize(user.getTotpSecret(), code);
}
public static String getQRCodeURL(User user, GoogleAuthenticatorKey key) {
return GoogleAuthenticatorQRGenerator.getOtpAuthURL(
"MineHQ v3",
user.getLastUsername(),
key
);
}
}