Whoo more permission stuff

This commit is contained in:
Colin McDonald 2016-04-30 20:08:58 -04:00
parent 1d5d2d198b
commit 583f91b45e
42 changed files with 362 additions and 255 deletions

View File

@ -5,4 +5,7 @@ mongo.username=test
mongo.password=test
http.address=
http.port=80
mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ
http.workerThreads=4
mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ
auth.permittedUserRanks=developer,owner
auth.websiteApiKey=RVbp4hY6sCFVaf

View File

@ -11,9 +11,6 @@ import net.frozenorb.apiv3.filters.ActorAttributeFilter;
import net.frozenorb.apiv3.filters.AuthorizationFilter;
import net.frozenorb.apiv3.filters.ContentTypeFilter;
import net.frozenorb.apiv3.routes.GETDump;
import net.frozenorb.apiv3.routes.GETRoutes;
import net.frozenorb.apiv3.routes.POSTConfirmRegister;
import net.frozenorb.apiv3.routes.POSTHeartbeat;
import net.frozenorb.apiv3.routes.announcements.GETAnnouncements;
import net.frozenorb.apiv3.routes.chatFilterList.GETChatFilterList;
import net.frozenorb.apiv3.routes.grants.DELETEGrant;
@ -30,21 +27,23 @@ import net.frozenorb.apiv3.routes.serverGroups.GETServerGroup;
import net.frozenorb.apiv3.routes.serverGroups.GETServerGroups;
import net.frozenorb.apiv3.routes.servers.GETServer;
import net.frozenorb.apiv3.routes.servers.GETServers;
import net.frozenorb.apiv3.routes.servers.POSTServerHeartbeat;
import net.frozenorb.apiv3.routes.users.*;
import net.frozenorb.apiv3.serialization.FollowAnnotationExclusionStrategy;
import net.frozenorb.apiv3.serialization.ObjectIdTypeAdapter;
import net.frozenorb.apiv3.unsorted.LoggingExceptionHandler;
import org.bson.types.ObjectId;
import org.mongodb.morphia.Datastore;
import org.mongodb.morphia.Morphia;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.Properties;
import static spark.Spark.*;
public final class APIv3 {
// TODO: Cleanup main class
@Getter private static Datastore datastore;
@Getter private static Properties config = new Properties();
private final Gson gson = new GsonBuilder()
@ -53,80 +52,74 @@ public final class APIv3 {
.create();
APIv3() {
try {
// TODO: Load
config.load(APIv3.class.getClassLoader().getResourceAsStream("apiv3.properties"));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
setupConfig();
setupDatabase();
setupHttp();
}
private void setupConfig() {
try (InputStream in = new FileInputStream("apiv3.properties")) {
config.load(in);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private void setupDatabase() {
MongoClient mongoClient = new MongoClient(new ServerAddress(
(String) config.get("mongo.address"),
(Integer) config.get("mongo.port")),
config.getProperty("mongo.address"),
Integer.parseInt(config.getProperty("mongo.port"))),
ImmutableList.of(
MongoCredential.createCredential(
(String) config.get("mongo.username"),
(String) config.get("mongo.database"),
((String) config.get("mongo.password")).toCharArray())
config.getProperty("mongo.username"),
config.getProperty("mongo.database"),
config.getProperty("mongo.password").toCharArray())
));
Morphia morphia = new Morphia();
morphia.mapPackage("net.frozenorb.apiv3.accessor");
datastore = morphia.createDatastore(mongoClient, (String) config.get("mongo.database"));
datastore = morphia.createDatastore(mongoClient, config.getProperty("mongo.database"));
datastore.ensureIndexes();
}
private void setupHttp() {
ipAddress((String) config.get("http.address"));
port((Integer) config.get("http.port"));
ipAddress(config.getProperty("http.address"));
port(Integer.parseInt(config.getProperty("http.port")));
threadPool(Integer.parseInt(config.getProperty("http.workerThreads")));
before(new ContentTypeFilter());
before(new ActorAttributeFilter());
before(new AuthorizationFilter());
exception(Exception.class, new LoggingExceptionHandler());
get("/announcements", new GETAnnouncements(), gson::toJson);
get("/chatFilterList", new GETChatFilterList(), gson::toJson);
delete("/grant/:id", new DELETEGrant(), gson::toJson);
get("/grants", new GETGrants(), gson::toJson);
get("/user/:id/grants", new GETUserGrants(), gson::toJson);
post("/user/:id:/grant", new POSTUserGrant(), gson::toJson);
get("/user/:id/ipLog", new GETUserIPLog(), gson::toJson);
delete("/punishment/:id", new DELETEPunishment(), gson::toJson);
get("/punishment/:id", new GETPunishment(), gson::toJson);
get("/punishments", new GETPunishments(), gson::toJson);
post("/user/:id:/punish", new POSTUserPunish(), gson::toJson);
get("/ranks", new GETRanks(), gson::toJson);
get("/serverGroup/:id", new GETServerGroup(), gson::toJson);
get("/serverGroups", new GETServerGroups(), gson::toJson);
get("/server/:id", new GETServer(), gson::toJson);
get("/servers", new GETServers(), gson::toJson);
post("/server/heartbeat", new POSTServerHeartbeat(), gson::toJson);
delete("/user/:id/meta", new DELETEUserMeta(), gson::toJson);
get("/staff", new GETStaff(), gson::toJson);
get("/user/:id", new GETUser(), gson::toJson);
get("/user/:id/details", new GETUserDetails(), gson::toJson);
get("/user/:id/meta/:serverGroup", new GETUserMeta(), gson::toJson);
post("/user/confirmRegister/:emailToken", new POSTUserConfirmRegister(), 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);
put("/user/:id/meta/:serverGroup", new PUTUserMeta(), gson::toJson);
get("/dump/:type", new GETDump(), gson::toJson);
get("/routes", new GETRoutes());
post("/confirmRegister", new POSTConfirmRegister(), gson::toJson);
post("/heartbeat", new POSTHeartbeat(), gson::toJson);
}
}

View File

@ -1,6 +1,6 @@
package net.frozenorb.apiv3;
public final class Main {
final class Main {
public static void main(String[] args) {
new APIv3();

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.unsorted;
package net.frozenorb.apiv3.actors;
public interface Actor {

View File

@ -0,0 +1,25 @@
package net.frozenorb.apiv3.actors;
import net.frozenorb.apiv3.models.Server;
public final class ServerActor implements Actor {
private final Server server;
public ServerActor(Server server) {
this.server = server;
}
public boolean isAuthorized() {
return true;
}
public String getName() {
return server.getId();
}
public Actor.Type getType() {
return Actor.Type.SERVER;
}
}

View File

@ -0,0 +1,17 @@
package net.frozenorb.apiv3.actors;
public final class UnknownActor implements Actor {
public boolean isAuthorized() {
return false;
}
public String getName() {
return "Unknown";
}
public Actor.Type getType() {
return Actor.Type.UNKNOWN;
}
}

View File

@ -0,0 +1,39 @@
package net.frozenorb.apiv3.actors;
import com.google.common.collect.ImmutableSet;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.models.User;
import java.util.Set;
public final class UserActor implements Actor {
private static final Set<String> authorizedUserGrants = ImmutableSet.copyOf(APIv3.getConfig().getProperty("auth.permittedUserRanks").split(","));
private final User user;
// We use Boolean here so we can have null = not calculated;
private Boolean cachedAuthorized = null;
public UserActor(User user) {
this.user = user;
}
public boolean isAuthorized() {
if (cachedAuthorized != null) {
return cachedAuthorized;
} else {
String highestRankId = user.getHighestRank().getId();
cachedAuthorized = authorizedUserGrants.contains(highestRankId);
return cachedAuthorized;
}
}
public String getName() {
return user.getLastUsername();
}
public Actor.Type getType() {
return Actor.Type.USER;
}
}

View File

@ -0,0 +1,17 @@
package net.frozenorb.apiv3.actors;
public final class WebsiteActor implements Actor {
public boolean isAuthorized() {
return true;
}
public String getName() {
return "Website";
}
public Actor.Type getType() {
return Actor.Type.WEBSITE;
}
}

View File

@ -1,33 +1,74 @@
package net.frozenorb.apiv3.filters;
import net.frozenorb.apiv3.unsorted.Actor;
import com.sun.xml.internal.messaging.saaj.util.Base64;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.*;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.User;
import spark.Filter;
import spark.Request;
import spark.Response;
import static spark.Spark.halt;
public final class ActorAttributeFilter implements Filter {
public void handle(Request req, Response res) {
// TODO: IF THEYRE A SERVER PERFORM PRE-VALIDATION TO MAKE SURE IT EXISTS
// TODO: Auth!
req.attribute("actor", new Actor() {
String authHeader = req.headers("Authorization");
String mhqAuthHeader = req.headers("MHQ-Authorization");
@Override
public boolean isAuthorized() {
return true;
if (authHeader != null) {
req.attribute("actor", processBasicAuthorization(authHeader));
} else if (mhqAuthHeader != null) {
req.attribute("actor", processMHQAuthorization(mhqAuthHeader));
} else {
req.attribute("actor", new UnknownActor());
}
}
private Actor processBasicAuthorization(String authHeader) {
String encodedHeader = authHeader.substring("Basic ".length());
String[] credentials = Base64.base64Decode(encodedHeader).split(":");
if (credentials.length == 2) {
User user = User.byLastUsername(credentials[0]);
char[] password = credentials[1].toCharArray();
if (user != null && user.checkPassword(password)) {
return new UserActor(user);
}
}
@Override
public String getName() {
return "HCTeams";
halt(401);
return null;
}
private Actor processMHQAuthorization(String authHeader) {
String[] split = authHeader.split(" ");
if (split.length >= 2) {
String type = split[0];
if (type.equals("Website") && split.length == 2) {
String givenKey = split[1];
String properKey = APIv3.getConfig().getProperty("auth.websiteApiKey");
if (givenKey.equals(properKey)) {
return new WebsiteActor();
}
} else if (type.equals("Server") && split.length == 3) {
Server server = Server.byId(split[1]);
String givenKey = split[2];
String properKey = server.getApiKey();
if (givenKey.equals(properKey)) {
return new ServerActor(server);
}
}
}
@Override
public Actor.Type getType() {
return Actor.Type.SERVER;
}
});
halt(401);
return null;
}
}

View File

@ -1,6 +1,6 @@
package net.frozenorb.apiv3.filters;
import net.frozenorb.apiv3.unsorted.Actor;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Filter;
import spark.Request;
@ -10,7 +10,7 @@ import spark.Spark;
public final class AuthorizationFilter implements Filter {
public void handle(Request req, Response res) {
Actor actor = req.attribute("actor");
Actor actor = req.attribute("actors");
if (!actor.isAuthorized()) {
Spark.halt(ErrorUtils.error("Unauthorized access: Please authenticate as either a server, the website, or an authorized user.").toJson());

View File

@ -1,7 +1,7 @@
package net.frozenorb.apiv3.models;
import lombok.Getter;
import net.frozenorb.apiv3.unsorted.Actor;
import net.frozenorb.apiv3.actors.Actor;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.mongodb.morphia.annotations.Entity;
@ -27,7 +27,7 @@ public final class AuditLogEntry {
this.performedAt = new Date();
this.actorName = actor.getName();
this.actionType = actionType;
this.actionData = actionData;
this.actionData = new Document(actionData);
}
}

View File

@ -40,7 +40,7 @@ public final class Grant {
this.reason = reason;
this.scopes = new HashSet<>(Collections2.transform(scopes, ServerGroup::getId));
this.rank = rank.getId();
this.expiresAt = expiresAt;
this.expiresAt = (Date) expiresAt.clone();
this.addedBy = addedBy.getId();
this.addedAt = new Date();
}
@ -69,10 +69,6 @@ public final class Grant {
return removedBy != null;
}
public boolean appliesOn(Server server) {
return isGlobal() || scopes.contains(server.getId());
}
public boolean appliesOn(ServerGroup serverGroup) {
return isGlobal() || scopes.contains(serverGroup.getId());
}

View File

@ -36,7 +36,7 @@ public final class Punishment {
this.target = target.getId();
this.reason = reason;
this.type = type;
this.expiresAt = expiresAt;
this.expiresAt = (Date) expiresAt.clone();
this.addedBy = addedBy.getId();
this.addedAt = new Date();
this.addedOn = addedOn.getId();

View File

@ -22,7 +22,7 @@ public final class Rank {
}
public static List<Rank> values() {
return APIv3.getDatastore().createQuery(Rank.class).asList();
return APIv3.getDatastore().createQuery(Rank.class).order("weight").asList();
}
public Rank() {} // For Morphia

View File

@ -14,7 +14,7 @@ public final class Server {
@Getter @Id private String id;
@Getter private String bungeeId;
@Getter private String displayName;
@Getter private String secret;
@Getter private String apiKey;
@Getter private String group;
@Getter private String ip;
@Getter @Setter private Date lastUpdate;
@ -31,11 +31,11 @@ public final class Server {
public Server() {} // For Morphia
public Server(String id, String bungeeId, String displayName, String secret, ServerGroup group, String ip) {
public Server(String id, String bungeeId, String displayName, String apiKey, ServerGroup group, String ip) {
this.id = id;
this.bungeeId = bungeeId;
this.displayName = displayName;
this.secret = secret;
this.apiKey = apiKey;
this.group = group.getId();
this.ip = ip;
this.lastUpdate = new Date();
@ -43,8 +43,4 @@ public final class Server {
this.players = new HashSet<>();
}
public ServerGroup resolveGroup() {
return ServerGroup.byId(group);
}
}

View File

@ -1,7 +1,9 @@
package net.frozenorb.apiv3.models;
import com.google.common.collect.ImmutableSet;
import lombok.Getter;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.utils.PermissionUtils;
import org.mongodb.morphia.annotations.Entity;
import org.mongodb.morphia.annotations.Id;
@ -14,6 +16,7 @@ public final class ServerGroup {
@Getter private String displayName;
@Getter private Set<String> announcements;
@Getter private Set<String> chatFilterList;
@Getter private Map<String, List<String>> permissions;
public static ServerGroup byId(String id) {
return APIv3.getDatastore().createQuery(ServerGroup.class).field("id").equalIgnoreCase(id).get();
@ -30,41 +33,24 @@ public final class ServerGroup {
this.displayName = displayName;
this.announcements = new HashSet<>();
this.chatFilterList = new HashSet<>();
this.permissions = new HashMap<>();
}
public void setAnnouncements(Set<String> announcements) {
this.announcements = announcements;
this.announcements = ImmutableSet.copyOf(announcements);
APIv3.getDatastore().save(this);
}
public void setChatFilterList(Set<String> chatFilterList) {
this.chatFilterList = chatFilterList;
this.chatFilterList = ImmutableSet.copyOf(chatFilterList);
APIv3.getDatastore().save(this);
}
// TODO: DO THIS
public Map<String, Boolean> getPermissions(Rank rank) {
return new HashMap<>();
}
public Map<String, Boolean> calculatePermissions(Rank userRank) {
/*ServerGroup scope = ServerGroup.byId("HCTeams");
Map<ServerGroup, Rank> userRanks = new HashMap<>();
//ServerGroup global = ServerGroup.byId("Global");
Map<String, Boolean> calculatedPermissions = global.getPermissions(userRanks.get(global));
// for global calculations
for (ServerGroup serverGroup : ServerGroup.values()) {
mergePermissions(serverGroup, userRanks.get(serverGroup), calculatedPermissions);
}
// for scoped calculations
mergePermissions(scope, userRanks.get(scope), calculatedPermissions);*/
return null;
return PermissionUtils.mergePermissions(
PermissionUtils.getDefaultPermissions(userRank),
PermissionUtils.mergeUpTo(permissions, userRank)
);
}
}

View File

@ -18,7 +18,7 @@ import java.util.*;
public final class User {
@Getter @Id private UUID id;
@Getter private String lastName;
@Getter private String lastUsername;
@Getter @ExcludeFromReplies private Map<String, Date> aliases;
@Getter @ExcludeFromReplies private String otpCode;
@Getter @ExcludeFromReplies @Setter private String emailToken;
@ -42,31 +42,20 @@ public final class User {
return APIv3.getDatastore().createQuery(User.class).field("id").equal(id).get();
}
// TODO: FIND ALL USAGES OF THIS AND
// SEE HOW MANY OF THEM (EX NON GET REQUESTS)
// WE CAN DROP
public static User byIdOrName(String idOrName) {
if (idOrName.length() == 36) {
return byId(UUID.fromString(idOrName));
} else {
return byName(idOrName);
}
}
@Deprecated
public static User byName(String name) {
return APIv3.getDatastore().createQuery(User.class).field("lastName").equalIgnoreCase(name).get();
}
public static User byEmailToken(String name) {
return APIv3.getDatastore().createQuery(User.class).field("emailToken").equal(name).get();
}
@Deprecated
public static User byLastUsername(String lastUsername) {
return APIv3.getDatastore().createQuery(User.class).field("lastUsername").equal(lastUsername).get();
}
public User() {} // For Morphia
public User(UUID id, String lastName) {
public User(UUID id, String lastUsername) {
this.id = id;
this.lastName = lastName;
this.lastUsername = lastUsername;
this.aliases = new HashMap<>();
this.otpCode = null;
this.password = null;
@ -76,18 +65,18 @@ public final class User {
this.lastSeenAt = new Date();
this.firstSeen = new Date();
aliases.put(lastName, new Date());
}
public boolean hasPermissionAnywhere(String permission) {
return hasPermissionScoped(permission, null);
aliases.put(lastUsername, new Date());
}
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;
Map<String, Boolean> permissions = scope.calculatePermissions(getHighestRank(scope));
return permissions.containsKey(permission) && permissions.get(permission);
}
// TODO
public boolean hasPermissionAnywhere(String permission) {
Map<String, Boolean> permissions = /*scope.calculatePermissions(getHighestRank(scope));*/ ImmutableMap.of();
return permissions.containsKey(permission) && permissions.get(permission);
}
public List<Grant> getGrants() {
@ -98,6 +87,17 @@ public final class User {
return APIv3.getDatastore().createQuery(IPLogEntry.class).field("user").equal(id).asList();
}
public IPLogEntry getIPLogEntry(String ip) {
IPLogEntry existing = APIv3.getDatastore().createQuery(IPLogEntry.class).field("user").equal(id).field("ip").equal(ip).get();
if (existing == null) {
existing = new IPLogEntry(this, ip);
APIv3.getDatastore().save(existing);
}
return existing;
}
public List<Punishment> getPunishments() {
return APIv3.getDatastore().createQuery(Punishment.class).field("target").equal(id).asList();
}
@ -121,6 +121,11 @@ public final class User {
}
}
public void seenOnServer(Server server) {
this.lastSeenOn = server.getId();
this.lastSeenAt = new Date();
}
public void setPassword(char[] unencrypted) {
this.password = BCrypt.hashpw(new String(unencrypted), BCrypt.gensalt());
}
@ -133,11 +138,7 @@ public final class User {
Rank highest = null;
for (Grant grant : getGrants()) {
if (!grant.isActive()) {
continue;
}
if (serverGroup != null && !grant.appliesOn(serverGroup)) {
if (!grant.isActive() || (serverGroup != null && !grant.appliesOn(serverGroup))) {
continue;
}
@ -155,7 +156,7 @@ public final class User {
}
}
public Rank getHighestRankAnywhere() {
public Rank getHighestRank() {
return getHighestRank(null);
}
@ -184,7 +185,7 @@ public final class User {
}
ServerGroup actorGroup = server.resolveGroup();
ServerGroup actorGroup = ServerGroup.byId(server.getGroup());
Rank rank = getHighestRank(actorGroup);
Map<String, Boolean> rankPermissions = actorGroup.calculatePermissions(rank);

View File

@ -23,7 +23,7 @@ public final class UserMetaEntry {
public UserMetaEntry(User user, ServerGroup serverGroup, Document data) {
this.user = user.getId();
this.serverGroup = serverGroup.getId();
this.data = data;
this.data = new Document(data);
}
public void delete() {

View File

@ -1,72 +0,0 @@
package net.frozenorb.apiv3.routes;
import spark.Request;
import spark.Response;
import spark.Route;
import spark.Spark;
import spark.route.HttpMethod;
import spark.route.SimpleRouteMatcher;
import java.lang.reflect.Field;
import java.util.List;
import static spark.Spark.halt;
public final class GETRoutes implements Route {
private List<Object> routes;
private Field httpMethodField;
private Field pathField;
private Field targetField;
@SuppressWarnings("unchecked") // Casting List to List<Object>
public GETRoutes() {
try {
Object spark = Spark.getInstance();
Field routeMatcherField = spark.getClass().getDeclaredField("routeMatcher");
routeMatcherField.setAccessible(true);
SimpleRouteMatcher routeMatcher = (SimpleRouteMatcher) routeMatcherField.get(spark);
Field routesField = routeMatcher.getClass().getDeclaredField("routes");
routesField.setAccessible(true);
routes = (List<Object>) routesField.get(routeMatcher);
Class<?> routeEntryClass = Class.forName("spark.route.RouteEntry");
httpMethodField = routeEntryClass.getDeclaredField("httpMethod");
httpMethodField.setAccessible(true);
pathField = routeEntryClass.getDeclaredField("path");
pathField.setAccessible(true);
targetField = routeEntryClass.getDeclaredField("target");
targetField.setAccessible(true);
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
public Object handle(Request req, Response res) {
try {
String response = "";
for (Object route : routes) {
HttpMethod httpMethod = (HttpMethod) httpMethodField.get(route);
String path = (String) pathField.get(route);
Object target = targetField.get(route);
if (httpMethod == HttpMethod.before || httpMethod == HttpMethod.after) {
continue;
}
String append = httpMethod.name().toUpperCase() + " " + path + " (" + target + ")<br />";
response += append;
}
halt(response); // We use halt to avoid our content type filter (gson serialization is already avoided because we don't have the filter on this route)
return null; // Return is required but will never be reached
} catch (IllegalAccessException ex) { // If we use a generic Exception we'd catch the halt() call (it throws an exception)
ex.printStackTrace();
return ex;
}
}
}

View File

@ -10,7 +10,7 @@ public final class GETAnnouncements implements Route {
public Object handle(Request req, Response res) {
Server sender = req.attribute("server");
ServerGroup senderGroup = sender.resolveGroup();
ServerGroup senderGroup = ServerGroup.byId(sender.getGroup());
return senderGroup.getAnnouncements();
}

View File

@ -10,7 +10,7 @@ public final class GETChatFilterList implements Route {
public Object handle(Request req, Response res) {
Server sender = req.attribute("server");
ServerGroup senderGroup = sender.resolveGroup();
ServerGroup senderGroup = ServerGroup.byId(sender.getGroup());
return senderGroup.getChatFilterList();
}

View File

@ -18,7 +18,7 @@ public final class DELETEGrant implements Route {
if (grant == null) {
return ErrorUtils.notFound("Grant", req.params("id"));
} else if (!grant.isActive()) {
return ErrorUtils.error("Cannot remove an inactive grant.");
return ErrorUtils.invalidInput("Cannot remove an inactive grant.");
}
User removedBy = User.byId(req.queryParams("removedBy"));
@ -33,7 +33,7 @@ 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()));
AuditLog.log(removedBy, req.attribute("actors"), "grant.remove", new Document("grantId", grant.getId()));
return grant;
}

View File

@ -9,7 +9,7 @@ import spark.Route;
public final class GETUserGrants implements Route {
public Object handle(Request req, Response res) {
User target = User.byIdOrName(req.params("id"));
User target = User.byId(req.params("id"));
if (target == null) {
return ErrorUtils.notFound("User", req.params("id"));

View File

@ -18,7 +18,7 @@ import java.util.Set;
public final class POSTUserGrant implements Route {
public Object handle(Request req, Response res) {
User target = User.byIdOrName(req.params("id"));
User target = User.byId(req.params("id"));
if (target == null) {
return ErrorUtils.notFound("User", req.params("id"));
@ -30,8 +30,17 @@ public final class POSTUserGrant implements Route {
return ErrorUtils.invalidInput("A reason must be provided.");
}
Set<ServerGroup> scopes = new HashSet<>(/*Arrays.asList(req.queryParams("scopes").split(","))*/);
// TODO
Set<ServerGroup> scopes = new HashSet<>();
for (String serverGroupId : req.queryParams("scopes").split(",")) {
ServerGroup serverGroup = ServerGroup.byId(serverGroupId);
if (serverGroup == null) {
return ErrorUtils.notFound("Server group", serverGroupId);
}
scopes.add(serverGroup);
}
Rank rank = Rank.byId(req.queryParams("rank"));

View File

@ -9,7 +9,7 @@ import spark.Route;
public final class GETUserIPLog implements Route {
public Object handle(Request req, Response res) {
User target = User.byIdOrName(req.params("id"));
User target = User.byId(req.params("id"));
if (target == null) {
return ErrorUtils.notFound("User", req.params("id"));

View File

@ -18,7 +18,7 @@ public final class DELETEPunishment implements Route {
if (punishment == null) {
return ErrorUtils.notFound("Punishment", req.params("id"));
} else if (!punishment.isActive()) {
return ErrorUtils.error("Cannot remove an inactive punishment.");
return ErrorUtils.invalidInput("Cannot remove an inactive punishment.");
}
User removedBy = User.byId(req.queryParams("removedBy"));
@ -33,7 +33,7 @@ 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()));
AuditLog.log(removedBy, req.attribute("actors"), "punishment.remove", new Document("punishmentId", punishment.getId()));
return punishment;
}

View File

@ -15,7 +15,7 @@ import java.util.Date;
public final class POSTUserPunish implements Route {
public Object handle(Request req, Response res) {
User target = User.byIdOrName(req.params("id"));
User target = User.byId(req.params("id"));
if (target == null) {
return ErrorUtils.notFound("User", req.params("id"));

View File

@ -1,10 +1,10 @@
package net.frozenorb.apiv3.routes;
package net.frozenorb.apiv3.routes.servers;
import com.google.common.collect.ImmutableMap;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Actor;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.Document;
import spark.Request;
@ -13,10 +13,11 @@ import spark.Route;
import java.util.*;
public final class POSTHeartbeat implements Route {
public final class POSTServerHeartbeat implements Route {
@SuppressWarnings("unchecked")
public Object handle(Request req, Response res) {
Actor actor = req.attribute("actor");
Actor actor = req.attribute("actors");
if (actor.getType() != Actor.Type.SERVER) {
return ErrorUtils.error("Heartbeats can only be performed when requested by a server.");
@ -27,18 +28,19 @@ public final class POSTHeartbeat implements Route {
Set<UUID> onlinePlayers = new HashSet<>();
Map<String, Object> playersResponse = new HashMap<>();
// TODO: SAW ON SERVER STUFF
for (Object player : (List<Object>) reqJson.get("players")) {
Document playerJson = (Document) player;
User user = User.byId(playerJson.getString("uuid"));
String username = playerJson.getString("username");
if (user == null) {
// Will be saved by the save command a few lines down.
user = new User(UUID.fromString(playerJson.getString("uuid")), username);
APIv3.getDatastore().save(user);
}
user.seenOnServer(actorServer);
APIv3.getDatastore().save(user);
onlinePlayers.add(user.getId());
playersResponse.put(user.getId().toString(), user.getLoginInfo(actorServer));
}
@ -62,6 +64,7 @@ public final class POSTHeartbeat implements Route {
actorServer.setPlayers(onlinePlayers);
actorServer.setLastTps(reqJson.getDouble("lastTps"));
actorServer.setLastUpdate(new Date());
APIv3.getDatastore().save(actorServer);
return ImmutableMap.of(
"players", playersResponse

View File

@ -11,7 +11,7 @@ import spark.Route;
public final class DELETEUserMeta implements Route {
public Object handle(Request req, Response res) {
User user = User.byIdOrName(req.params("id"));
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));

View File

@ -8,7 +8,7 @@ import spark.Route;
public final class GETUser implements Route {
public Object handle(Request req, Response res) {
return User.byIdOrName(req.params("id"));
return User.byId(req.params("id"));
}
}

View File

@ -10,7 +10,7 @@ import spark.Route;
public final class GETUserDetails implements Route {
public Object handle(Request req, Response res) {
User user = User.byIdOrName(req.params("id"));
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));

View File

@ -11,7 +11,7 @@ import spark.Route;
public final class GETUserMeta implements Route {
public Object handle(Request req, Response res) {
User user = User.byIdOrName(req.params("id"));
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));

View File

@ -1,4 +1,4 @@
package net.frozenorb.apiv3.routes;
package net.frozenorb.apiv3.routes.users;
import com.google.common.collect.ImmutableList;
import net.frozenorb.apiv3.APIv3;
@ -12,9 +12,9 @@ import spark.Route;
import java.util.List;
import java.util.concurrent.TimeUnit;
public final class POSTConfirmRegister implements Route {
public final class POSTUserConfirmRegister implements Route {
private List<String> commonPasswords = ImmutableList.copyOf(("123456 password 12345678 qwerty 123456789 12345 1234 111111 1234567 dragon " +
private final List<String> commonPasswords = ImmutableList.copyOf(("123456 password 12345678 qwerty 123456789 12345 1234 111111 1234567 dragon " +
"123123 baseball abc123 football monkey letmein 696969 shadow master 666666 qwertyuiop 123321 mustang 1234567890 " +
"michael 654321 pussy superman 1qaz2wsx 7777777 fuckyou 121212 000000 qazwsx 123qwe killer trustno1 jordan jennifer " +
"zxcvbnm asdfgh hunter buster soccer harley batman andrew tigger sunshine iloveyou fuckme 2000 charlie robert thomas " +

View File

@ -1,9 +1,9 @@
package net.frozenorb.apiv3.routes.users;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.models.Server;
import net.frozenorb.apiv3.models.User;
import net.frozenorb.apiv3.unsorted.Actor;
import net.frozenorb.apiv3.utils.ErrorUtils;
import spark.Request;
import spark.Response;
@ -16,20 +16,21 @@ public final class POSTUserLoginInfo implements Route {
public Object handle(Request req, Response res) {
User user = User.byId(req.params("id"));
String username = req.queryParams("username");
Actor actor = req.attribute("actor");
String userIp = req.queryParams("userIp");
Actor actor = req.attribute("actors");
if (actor.getType() != Actor.Type.SERVER) {
return ErrorUtils.error("Login info requests can only be performed when requested by a server.");
}
// TODO: IP Logs
if (user == null) {
user = new User(UUID.fromString(req.params("id")), username);
APIv3.getDatastore().save(user);
}
Server actorServer = Server.byId(actor.getName());
user.getIPLogEntry(userIp).used();
return user.getLoginInfo(actorServer);
}

View File

@ -15,7 +15,7 @@ import java.util.Map;
public final class POSTUserNotify implements Route {
public Object handle(Request req, Response res) {
User user = User.byIdOrName(req.params("id"));
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));
@ -48,6 +48,7 @@ public final class POSTUserNotify implements Route {
notification.sendAsEmail(user.getEmail());
return new Document("success", true);
} catch (Exception ex) {
ex.printStackTrace();
return ErrorUtils.error("Failed to send notification");
}
}

View File

@ -20,18 +20,18 @@ import java.util.regex.Pattern;
public final class POSTUserRegister implements Route {
public static final Pattern VALID_EMAIL_ADDRESS_REGEX =
private static final Pattern VALID_EMAIL_ADDRESS_REGEX =
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
public Object handle(Request req, Response res) {
User user = User.byIdOrName(req.params("id"));
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));
}
if (user.getEmail() != null) {
return ErrorUtils.error("User provided already has email set.");
return ErrorUtils.invalidInput("User provided already has email set.");
}
String email = req.queryParams("email");
@ -50,7 +50,7 @@ public final class POSTUserRegister implements Route {
APIv3.getDatastore().save(user);
Map<String, Object> replacements = ImmutableMap.of(
"username", user.getLastName(),
"username", user.getLastUsername(),
"email", user.getEmail(),
"emailToken", user.getEmailToken()
);
@ -61,6 +61,7 @@ public final class POSTUserRegister implements Route {
notification.sendAsEmail(user.getEmail());
return new Document("success", true).append("message", "User registered");
} catch (Exception ex) {
ex.printStackTrace();
return ErrorUtils.error("Failed to send confirmation email. Please contact a MineHQ staff member.");
}
}

View File

@ -11,7 +11,7 @@ import spark.Route;
public final class PUTUserMeta implements Route {
public Object handle(Request req, Response res) {
User user = User.byIdOrName(req.params("id"));
User user = User.byId(req.params("id"));
if (user == null) {
return ErrorUtils.notFound("User", req.params("id"));

View File

@ -2,6 +2,7 @@ package net.frozenorb.apiv3.unsorted;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.APIv3;
import net.frozenorb.apiv3.actors.Actor;
import net.frozenorb.apiv3.models.AuditLogEntry;
import net.frozenorb.apiv3.models.User;
import org.bson.Document;

View File

@ -0,0 +1,20 @@
package net.frozenorb.apiv3.unsorted;
import net.frozenorb.apiv3.utils.ErrorUtils;
import org.bson.types.ObjectId;
import spark.ExceptionHandler;
import spark.Request;
import spark.Response;
public final class LoggingExceptionHandler implements ExceptionHandler {
public void handle(Exception ex, Request req, Response res) {
String code = new ObjectId().toHexString();
System.out.println(code + ":");
ex.printStackTrace();
res.body(ErrorUtils.error("An unknown error has occurred. Please contact a developer with the code \"" + code + "\".").toJson());
}
}

View File

@ -12,12 +12,12 @@ import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import java.io.IOException;
import java.util.Map;
public final class Notification {
public final class Notification {
private static MandrillMessagesRequest messagesRequest = MandrillUtils.createMessagesRequest();
private static final MandrillMessagesRequest messagesRequest = MandrillUtils.createMessagesRequest();
private String subject;
private String body;
private final String subject;
private final String body;
public Notification(NotificationTemplate template, Map<String, Object> subjectReplacements, Map<String, Object> bodyReplacements) {
this.subject = template.fillSubject(subjectReplacements);

View File

@ -1,22 +1,58 @@
package net.frozenorb.apiv3.utils;
import com.google.common.collect.ImmutableMap;
import lombok.experimental.UtilityClass;
import net.frozenorb.apiv3.models.Rank;
import net.frozenorb.apiv3.models.ServerGroup;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@UtilityClass
public class PermissionUtils {
public static void mergePermissions(ServerGroup serverGroup, Rank rank, Map<String, Boolean> current) {
Map<String, Boolean> groupPermissions = serverGroup.getPermissions(rank);
public static Map<String, Boolean> mergePermissions(Map<String, Boolean> current, Map<String, Boolean> merge) {
Map<String, Boolean> result = new HashMap<>(current);
groupPermissions.forEach((permissionNode, grant) -> {
if (!current.containsKey(permissionNode) || !current.get(permissionNode)) {
current.put(permissionNode, grant);
result.putAll(merge);
return result;
}
public static Map<String, Boolean> mergeUpTo(Map<String, List<String>> collection, Rank upTo) {
Map<String, Boolean> result = new HashMap<>();
for (Rank rank : Rank.values()) {
Map<String, Boolean> rankPermissions = convertToMap(collection.get(rank.getId()));
mergePermissions(result, rankPermissions);
if (upTo.getId().equals(rank.getId())) {
break;
}
});
}
return result;
}
// TODO: Fix this
public static Map<String, Boolean> getDefaultPermissions(Rank rank) {
return ImmutableMap.of();
}
private static Map<String, Boolean> convertToMap(List<String> unconvered) {
Map<String, Boolean> result = new HashMap<>();
for (String permission : unconvered) {
boolean negate = permission.startsWith("-");
if (negate) {
result.put(permission.substring(1), false);
} else {
result.put(permission, true);
}
}
return result;
}
}

View File

@ -2,14 +2,11 @@ package net.frozenorb.apiv3.utils;
import lombok.experimental.UtilityClass;
import java.text.SimpleDateFormat;
import java.util.Date;
@UtilityClass
public class TimeUtils {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("MM/dd/yyyy HH:mm");
public static String formatIntoDetailedString(int secs) {
if (secs == 0) {
return "0 seconds";
@ -30,10 +27,6 @@ public class TimeUtils {
return (fDays + fHours + fMinutes + fSeconds).trim();
}
public static String formatIntoCalendarString(Date date) {
return (dateFormat.format(date));
}
public static int getSecondsBetween(Date a, Date b) {
return (Math.abs((int) (a.getTime() - b.getTime()) / 1000));
}