More work
This commit is contained in:
parent
03bc01e5ce
commit
04e4db8fcd
@ -8,6 +8,8 @@ redis.port=6379
|
||||
http.address=
|
||||
http.port=80
|
||||
http.workerThreads=6
|
||||
twillio.accountSID=AC9e2f88c5690134d29a56f698de3cd740
|
||||
twillio.authToken=982592505a171d3be6b0722f5ecacc0e
|
||||
mandrill.apiKey=0OYtwymqJP6oqvszeJu0vQ
|
||||
auth.permittedUserRanks=developer,owner
|
||||
auth.websiteApiKey=RVbp4hY6sCFVaf
|
10
pom.xml
10
pom.xml
@ -112,6 +112,16 @@
|
||||
<artifactId>morphia-logging-slf4j</artifactId>
|
||||
<version>1.1.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-simple</artifactId>
|
||||
<version>1.6.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
<version>4.4</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.warrenstrange</groupId>
|
||||
<artifactId>googleauth</artifactId>
|
||||
|
@ -1,6 +1,7 @@
|
||||
package net.frozenorb.apiv3;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.mongodb.MongoClient;
|
||||
@ -10,6 +11,7 @@ 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.NotificationTemplate;
|
||||
import net.frozenorb.apiv3.routes.GETDump;
|
||||
import net.frozenorb.apiv3.routes.GETWhoAmI;
|
||||
import net.frozenorb.apiv3.routes.NotFound;
|
||||
@ -39,6 +41,7 @@ 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 net.frozenorb.apiv3.unsorted.Notification;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.mongodb.morphia.Datastore;
|
||||
import org.mongodb.morphia.Morphia;
|
||||
@ -164,6 +167,7 @@ public final class APIv3 {
|
||||
get("/user/:id/grants", new GETUserGrants(), 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);
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.frozenorb.apiv3.auditLog;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import net.frozenorb.apiv3.actors.Actor;
|
||||
@ -13,7 +14,7 @@ import java.util.Map;
|
||||
public class AuditLog {
|
||||
|
||||
public static void log(User performedBy, String performedByIp, Actor actor, AuditLogActionType actionType) {
|
||||
log(performedBy, performedByIp, actor, actionType, new Document());
|
||||
log(performedBy, performedByIp, actor, actionType, ImmutableMap.of());
|
||||
}
|
||||
|
||||
public static void log(User performedBy, String performedByIp, Actor actor, AuditLogActionType actionType, Map<String, Object> actionData) {
|
||||
|
@ -3,6 +3,8 @@ package net.frozenorb.apiv3.models;
|
||||
import com.google.common.collect.Collections2;
|
||||
import lombok.Getter;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import net.frozenorb.apiv3.actors.Actor;
|
||||
import net.frozenorb.apiv3.actors.ActorType;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.mongodb.morphia.annotations.Entity;
|
||||
import org.mongodb.morphia.annotations.Id;
|
||||
|
@ -2,6 +2,7 @@ package net.frozenorb.apiv3.models;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.mongodb.morphia.annotations.Entity;
|
||||
import org.mongodb.morphia.annotations.Id;
|
||||
@ -14,7 +15,7 @@ import java.util.UUID;
|
||||
public final class IPLogEntry {
|
||||
|
||||
@Getter @Id private String id;
|
||||
@Getter @Indexed private UUID user;
|
||||
@Getter @ExcludeFromReplies @Indexed private UUID user;
|
||||
@Getter @Indexed private String ip;
|
||||
@Getter private Date firstSeen;
|
||||
@Getter private Date lastSeen;
|
||||
|
@ -2,6 +2,8 @@ package net.frozenorb.apiv3.models;
|
||||
|
||||
import lombok.Getter;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import net.frozenorb.apiv3.actors.Actor;
|
||||
import net.frozenorb.apiv3.actors.ActorType;
|
||||
import org.bson.types.ObjectId;
|
||||
import org.mongodb.morphia.annotations.Entity;
|
||||
import org.mongodb.morphia.annotations.Id;
|
||||
@ -21,7 +23,8 @@ public final class Punishment {
|
||||
|
||||
@Getter private UUID addedBy;
|
||||
@Getter @Indexed private Date addedAt;
|
||||
@Getter private String addedOn; // TODO: Make this store actor like the audit log?
|
||||
@Getter private String actorName;
|
||||
@Getter private ActorType actorType;
|
||||
|
||||
@Getter private UUID removedBy;
|
||||
@Getter private Date removedAt;
|
||||
@ -33,7 +36,7 @@ public final class Punishment {
|
||||
|
||||
public Punishment() {} // For Morphia
|
||||
|
||||
public Punishment(User target, String reason, PunishmentType type, Date expiresAt, User addedBy, Server addedOn) {
|
||||
public Punishment(User target, String reason, PunishmentType type, Date expiresAt, User addedBy, Actor actor) {
|
||||
this.id = new ObjectId().toString();
|
||||
this.target = target.getId();
|
||||
this.reason = reason;
|
||||
@ -41,7 +44,8 @@ public final class Punishment {
|
||||
this.expiresAt = (Date) expiresAt.clone();
|
||||
this.addedBy = addedBy.getId();
|
||||
this.addedAt = new Date();
|
||||
this.addedOn = addedOn.getId();
|
||||
this.actorName = actor.getName();
|
||||
this.actorType = actor.getType();
|
||||
}
|
||||
|
||||
public void delete(User removedBy, String reason) {
|
||||
|
@ -2,7 +2,9 @@ package net.frozenorb.apiv3.models;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
|
||||
import net.frozenorb.apiv3.utils.PermissionUtils;
|
||||
import org.mongodb.morphia.annotations.Entity;
|
||||
import org.mongodb.morphia.annotations.Id;
|
||||
@ -13,11 +15,10 @@ import java.util.*;
|
||||
public final class ServerGroup {
|
||||
|
||||
@Getter @Id private String id;
|
||||
@Getter private String displayName;
|
||||
@Getter 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 private Set<String> announcements = new HashSet<>();
|
||||
@Getter private Set<String> chatFilterList = new HashSet<>();
|
||||
@Getter @Setter @ExcludeFromReplies private Set<String> announcements = new HashSet<>();
|
||||
@Getter private Map<String, List<String>> permissions = new HashMap<>();
|
||||
|
||||
public static ServerGroup byId(String id) {
|
||||
@ -30,26 +31,13 @@ public final class ServerGroup {
|
||||
|
||||
public ServerGroup() {} // For Morphia
|
||||
|
||||
public ServerGroup(String id, String displayName) {
|
||||
public ServerGroup(String id, boolean isPublic) {
|
||||
this.id = id;
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public void setAnnouncements(Set<String> announcements) {
|
||||
this.announcements = ImmutableSet.copyOf(announcements);
|
||||
APIv3.getDatastore().save(this);
|
||||
}
|
||||
|
||||
public void setChatFilterList(Set<String> chatFilterList) {
|
||||
this.chatFilterList = ImmutableSet.copyOf(chatFilterList);
|
||||
APIv3.getDatastore().save(this);
|
||||
this.isPublic = isPublic;
|
||||
}
|
||||
|
||||
public Map<String, Boolean> calculatePermissions(Rank userRank) {
|
||||
return PermissionUtils.mergePermissions(
|
||||
PermissionUtils.getDefaultPermissions(userRank),
|
||||
PermissionUtils.mergeUpTo(permissions, userRank)
|
||||
);
|
||||
return PermissionUtils.mergeUpTo(permissions, userRank);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
|
@ -6,6 +6,7 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import net.frozenorb.apiv3.serialization.ExcludeFromReplies;
|
||||
import net.frozenorb.apiv3.utils.PermissionUtils;
|
||||
import net.frozenorb.apiv3.utils.TimeUtils;
|
||||
import org.bson.Document;
|
||||
import org.mindrot.jbcrypt.BCrypt;
|
||||
@ -26,7 +27,7 @@ public final class User {
|
||||
@Getter @ExcludeFromReplies @Setter private Date emailTokenSet;
|
||||
@Getter @ExcludeFromReplies private String password;
|
||||
@Getter @Setter private String email;
|
||||
@Getter private int phoneNumber;
|
||||
@Getter private String phoneNumber;
|
||||
@Getter private String lastSeenOn;
|
||||
@Getter private Date lastSeenAt;
|
||||
@Getter private Date firstSeen;
|
||||
@ -61,7 +62,7 @@ public final class User {
|
||||
this.totpSecret = null;
|
||||
this.password = null;
|
||||
this.email = null;
|
||||
this.phoneNumber = -1;
|
||||
this.phoneNumber = null;
|
||||
this.lastSeenOn = null;
|
||||
this.lastSeenAt = new Date();
|
||||
this.firstSeen = new Date();
|
||||
@ -70,14 +71,29 @@ public final class User {
|
||||
}
|
||||
|
||||
public boolean hasPermissionScoped(String permission, ServerGroup scope) {
|
||||
Map<String, Boolean> permissions = scope.calculatePermissions(getHighestRank(scope));
|
||||
return permissions.containsKey(permission) && permissions.get(permission);
|
||||
Rank highestRank = getHighestRank(scope);
|
||||
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
|
||||
PermissionUtils.getDefaultPermissions(highestRank),
|
||||
scope.calculatePermissions(highestRank)
|
||||
);
|
||||
|
||||
return scopedPermissions.containsKey(permission) && scopedPermissions.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);
|
||||
Map<String, Boolean> globalPermissions = PermissionUtils.getDefaultPermissions(getHighestRank());
|
||||
|
||||
for (Map.Entry<ServerGroup, Rank> serverGroupEntry : getHighestRanks().entrySet()) {
|
||||
ServerGroup serverGroup = serverGroupEntry.getKey();
|
||||
Rank rank = serverGroupEntry.getValue();
|
||||
|
||||
globalPermissions = PermissionUtils.mergePermissions(
|
||||
globalPermissions,
|
||||
serverGroup.calculatePermissions(rank)
|
||||
);
|
||||
}
|
||||
|
||||
return globalPermissions.containsKey(permission) && globalPermissions.get(permission);
|
||||
}
|
||||
|
||||
public List<Grant> getGrants() {
|
||||
@ -122,9 +138,10 @@ public final class User {
|
||||
}
|
||||
}
|
||||
|
||||
public void seenOnServer(Server server) {
|
||||
public void seenOnServer(String username, Server server) {
|
||||
this.lastSeenOn = server.getId();
|
||||
this.lastSeenAt = new Date();
|
||||
this.aliases.put(username, new Date());
|
||||
}
|
||||
|
||||
public void setPassword(char[] unencrypted) {
|
||||
@ -135,6 +152,10 @@ public final class User {
|
||||
return BCrypt.checkpw(new String(unencrypted), password);
|
||||
}
|
||||
|
||||
public Rank getHighestRank() {
|
||||
return getHighestRank(null);
|
||||
}
|
||||
|
||||
public Rank getHighestRank(ServerGroup serverGroup) {
|
||||
Rank highest = null;
|
||||
|
||||
@ -157,8 +178,30 @@ public final class User {
|
||||
}
|
||||
}
|
||||
|
||||
public Rank getHighestRank() {
|
||||
return getHighestRank(null);
|
||||
public Map<ServerGroup, Rank> getHighestRanks() {
|
||||
Map<ServerGroup, Rank> highestRanks = new HashMap<>();
|
||||
Rank defaultRank = Rank.byId("default");
|
||||
List<Grant> userGrants = getGrants();
|
||||
|
||||
for (ServerGroup serverGroup : ServerGroup.values()) {
|
||||
Rank highest = defaultRank;
|
||||
|
||||
for (Grant grant : userGrants) {
|
||||
if (!grant.isActive() || !grant.appliesOn(serverGroup)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Rank rank = Rank.byId(grant.getRank());
|
||||
|
||||
if (highest == null || rank.getWeight() > highest.getWeight()) {
|
||||
highest = rank;
|
||||
}
|
||||
}
|
||||
|
||||
highestRanks.put(serverGroup, highest);
|
||||
}
|
||||
|
||||
return highestRanks;
|
||||
}
|
||||
|
||||
public Map<String, Object> getLoginInfo(Server server) {
|
||||
@ -187,18 +230,21 @@ public final class User {
|
||||
|
||||
|
||||
ServerGroup actorGroup = ServerGroup.byId(server.getGroup());
|
||||
Rank rank = getHighestRank(actorGroup);
|
||||
Map<String, Boolean> rankPermissions = actorGroup.calculatePermissions(rank);
|
||||
Rank highestRank = getHighestRank(actorGroup);
|
||||
Map<String, Boolean> scopedPermissions = PermissionUtils.mergePermissions(
|
||||
PermissionUtils.getDefaultPermissions(highestRank),
|
||||
actorGroup.calculatePermissions(highestRank)
|
||||
);
|
||||
|
||||
return ImmutableMap.of(
|
||||
"user", this,
|
||||
"access", ImmutableMap.of(
|
||||
"allowed", accessDenialReason == null,
|
||||
"reason", accessDenialReason == null ? "Public server" : accessDenialReason
|
||||
"message", accessDenialReason == null ? "Public server" : accessDenialReason
|
||||
),
|
||||
"rank", rank,
|
||||
"permissions", rankPermissions,
|
||||
"totpRequired", getTotpSecret() != null
|
||||
"rank", highestRank.getId(),
|
||||
"permissions", scopedPermissions,
|
||||
"totpSetup", getTotpSecret() != null
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.frozenorb.apiv3.models;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
@ -9,6 +10,7 @@ import org.mongodb.morphia.annotations.Entity;
|
||||
import org.mongodb.morphia.annotations.Id;
|
||||
import org.mongodb.morphia.annotations.Indexed;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
|
||||
@Entity(value = "userMeta", noClassnameStored = true)
|
||||
@ -17,15 +19,15 @@ public final class UserMetaEntry {
|
||||
@Getter @Id private String id;
|
||||
@Getter @Indexed private UUID user;
|
||||
@Getter @Indexed private String serverGroup;
|
||||
@Getter @Setter private Document data;
|
||||
@Getter @Setter private Map<String, Object> data;
|
||||
|
||||
public UserMetaEntry() {} // For Morphia
|
||||
|
||||
public UserMetaEntry(User user, ServerGroup serverGroup, Document data) {
|
||||
public UserMetaEntry(User user, ServerGroup serverGroup, Map<String, Object> data) {
|
||||
this.id = new ObjectId().toString();
|
||||
this.user = user.getId();
|
||||
this.serverGroup = serverGroup.getId();
|
||||
this.data = new Document(data);
|
||||
this.data = ImmutableMap.copyOf(data);
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.frozenorb.apiv3.routes;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import net.frozenorb.apiv3.models.Grant;
|
||||
import net.frozenorb.apiv3.models.Punishment;
|
||||
@ -8,8 +9,7 @@ import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
public final class GETDump implements Route {
|
||||
|
||||
@ -21,27 +21,49 @@ public final class GETDump implements Route {
|
||||
case "ban":
|
||||
case "mute":
|
||||
case "warn":
|
||||
List<Punishment> activePunishments = new ArrayList<>();
|
||||
List<UUID> effectedUsers = new ArrayList<>();
|
||||
|
||||
APIv3.getDatastore().createQuery(Punishment.class).field("type").equal(type.toUpperCase()).forEach((punishment) -> {
|
||||
if (punishment.isActive()) {
|
||||
activePunishments.add(punishment);
|
||||
effectedUsers.add(punishment.getTarget());
|
||||
}
|
||||
});
|
||||
|
||||
return activePunishments;
|
||||
return effectedUsers;
|
||||
case "accessDeniable":
|
||||
// We have to name it effectedUsers2 because Java's
|
||||
// scoping in switch statements is really dumb.
|
||||
List<UUID> effectedUsers2 = new ArrayList<>();
|
||||
|
||||
APIv3.getDatastore().createQuery(Punishment.class).field("type").in(ImmutableSet.of(
|
||||
Punishment.PunishmentType.BAN,
|
||||
Punishment.PunishmentType.BLACKLIST
|
||||
)).forEach((punishment) -> {
|
||||
if (punishment.isActive()) {
|
||||
effectedUsers2.add(punishment.getTarget());
|
||||
}
|
||||
});
|
||||
|
||||
return effectedUsers2;
|
||||
case "grant":
|
||||
List<Grant> activeGrants = new ArrayList<>();
|
||||
Map<String, List<UUID>> grantDump = new HashMap<>();
|
||||
|
||||
APIv3.getDatastore().createQuery(Grant.class).forEach((grant) -> {
|
||||
if (grant.isActive()) {
|
||||
activeGrants.add(grant);
|
||||
List<UUID> users = grantDump.get(grant.getRank());
|
||||
|
||||
if (users == null) {
|
||||
users = new ArrayList<>();
|
||||
grantDump.put(grant.getRank(), users);
|
||||
}
|
||||
|
||||
users.add(grant.getTarget());
|
||||
}
|
||||
});
|
||||
|
||||
return activeGrants;
|
||||
return grantDump;
|
||||
default:
|
||||
return ErrorUtils.invalidInput(type + " is not a valid type. Not in [blacklist, ban, mute, warn, grant]");
|
||||
return ErrorUtils.invalidInput(type + " is not a valid type. Not in [blacklist, ban, mute, warn, accessDeniable, grant]");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.frozenorb.apiv3.routes.chatFilterList;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import net.frozenorb.apiv3.actors.Actor;
|
||||
import net.frozenorb.apiv3.actors.ActorType;
|
||||
import net.frozenorb.apiv3.models.Server;
|
||||
@ -12,16 +13,7 @@ import spark.Route;
|
||||
public final class GETChatFilterList implements Route {
|
||||
|
||||
public Object handle(Request req, Response res) {
|
||||
Actor actor = req.attribute("actor");
|
||||
|
||||
if (actor.getType() != ActorType.SERVER) {
|
||||
return ErrorUtils.serverOnly();
|
||||
}
|
||||
|
||||
Server sender = Server.byId(actor.getName());
|
||||
ServerGroup senderGroup = ServerGroup.byId(sender.getGroup());
|
||||
|
||||
return senderGroup.getChatFilterList();
|
||||
return ImmutableSet.of();
|
||||
}
|
||||
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
package net.frozenorb.apiv3.routes.grants;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.frozenorb.apiv3.auditLog.AuditLog;
|
||||
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
|
||||
import net.frozenorb.apiv3.models.Grant;
|
||||
@ -39,7 +40,7 @@ public final class DELETEGrant implements Route {
|
||||
|
||||
grant.delete(removedBy, reason);
|
||||
// TODO: Fix IP
|
||||
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_GRANT, new Document("grantId", grant.getId()));
|
||||
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_GRANT, ImmutableMap.of());
|
||||
return grant;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
package net.frozenorb.apiv3.routes.punishments;
|
||||
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.frozenorb.apiv3.auditLog.AuditLog;
|
||||
import net.frozenorb.apiv3.auditLog.AuditLogActionType;
|
||||
import net.frozenorb.apiv3.models.Punishment;
|
||||
@ -31,7 +32,7 @@ public final class DELETEPunishment implements Route {
|
||||
return ErrorUtils.unauthorized(requiredPermission);
|
||||
}
|
||||
|
||||
String reason = req.queryParams("removalReason");
|
||||
String reason = req.queryParams("reason");
|
||||
|
||||
if (reason == null || reason.trim().isEmpty()) {
|
||||
return ErrorUtils.requiredInput("reason");
|
||||
@ -39,7 +40,7 @@ public final class DELETEPunishment implements Route {
|
||||
|
||||
punishment.delete(removedBy, reason);
|
||||
// TODO: Fix IP
|
||||
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_PUNISHMENT, new Document("punishmentId", punishment.getId()));
|
||||
AuditLog.log(removedBy, "", req.attribute("actor"), AuditLogActionType.DELETE_PUNISHMENT, ImmutableMap.of());
|
||||
return punishment;
|
||||
}
|
||||
|
||||
|
@ -17,8 +17,6 @@ public final class POSTUserPunish implements Route {
|
||||
public Object handle(Request req, Response res) {
|
||||
User target = User.byId(req.params("id"));
|
||||
|
||||
// TODO: PROTECTED USERS
|
||||
|
||||
if (target == null) {
|
||||
return ErrorUtils.notFound("User", req.params("id"));
|
||||
}
|
||||
@ -45,13 +43,11 @@ public final class POSTUserPunish implements Route {
|
||||
return ErrorUtils.unauthorized(requiredPermission);
|
||||
}
|
||||
|
||||
Server addedOn = Server.byId(req.queryParams("addedOn"));
|
||||
|
||||
if (addedOn == null) {
|
||||
return ErrorUtils.notFound("Server", req.queryParams("addedOn"));
|
||||
if (target.hasPermissionAnywhere(Permissions.PROTECTED_PUNISHMENT)) {
|
||||
return ErrorUtils.error(target.getLastSeenOn() + " is protected from punishments.");
|
||||
}
|
||||
|
||||
Punishment punishment = new Punishment(target, reason, type, expiresAt, addedBy, addedOn);
|
||||
Punishment punishment = new Punishment(target, reason, type, expiresAt, addedBy, req.attribute("actor"));
|
||||
APIv3.getDatastore().save(punishment);
|
||||
return punishment;
|
||||
}
|
||||
|
@ -10,9 +10,9 @@ public final class POSTServerGroup implements Route {
|
||||
|
||||
public Object handle(Request req, Response res) {
|
||||
String id = req.queryParams("id");
|
||||
String displayName = req.queryParams("displayName");
|
||||
boolean isPublic = Boolean.valueOf(req.queryParams("public"));
|
||||
|
||||
ServerGroup serverGroup = new ServerGroup(id, displayName);
|
||||
ServerGroup serverGroup = new ServerGroup(id, isPublic);
|
||||
APIv3.getDatastore().save(serverGroup);
|
||||
return serverGroup;
|
||||
}
|
||||
|
@ -39,7 +39,7 @@ public final class POSTServerHeartbeat implements Route {
|
||||
user = new User(UUID.fromString(playerJson.getString("uuid")), username);
|
||||
}
|
||||
|
||||
user.seenOnServer(actorServer);
|
||||
user.seenOnServer(username, actorServer);
|
||||
APIv3.getDatastore().save(user);
|
||||
|
||||
onlinePlayers.add(user.getId());
|
||||
|
@ -16,13 +16,14 @@ public final class GETUserDetails implements Route {
|
||||
return ErrorUtils.notFound("User", req.params("id"));
|
||||
}
|
||||
|
||||
return ImmutableMap.of(
|
||||
"user", user,
|
||||
"grants", user.getGrants(),
|
||||
"ipLog", user.getIPLog(),
|
||||
"punishments", user.getPunishments(),
|
||||
"totpRequired", user.getTotpSecret() != null
|
||||
);
|
||||
// Too many fields to use .of()
|
||||
return ImmutableMap.builder()
|
||||
.put("user", user)
|
||||
.put("grants", user.getGrants())
|
||||
.put("ipLog", user.getIPLog())
|
||||
.put("punishments", user.getPunishments())
|
||||
.put("aliases", user.getAliases())
|
||||
.put("totpSetup", user.getTotpSecret() != null);
|
||||
}
|
||||
|
||||
}
|
@ -1,4 +1,47 @@
|
||||
package net.frozenorb.apiv3.routes.users;
|
||||
|
||||
public class GETUserRequiresTOTP {
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import net.frozenorb.apiv3.models.User;
|
||||
import net.frozenorb.apiv3.utils.ErrorUtils;
|
||||
import net.frozenorb.apiv3.utils.IPUtils;
|
||||
import net.frozenorb.apiv3.utils.TOTPUtils;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
|
||||
public final class GETUserRequiresTOTP 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 ImmutableMap.of(
|
||||
"required", false,
|
||||
"message", "User does not have TOTP setup."
|
||||
);
|
||||
}
|
||||
|
||||
String userIp = req.queryParams("userIp");
|
||||
|
||||
if (!IPUtils.isValidIP(userIp)) {
|
||||
return ErrorUtils.invalidInput("IP address \"" + userIp + "\" is not valid.");
|
||||
}
|
||||
|
||||
if (TOTPUtils.isPreAuthorized(user, userIp)) {
|
||||
return ImmutableMap.of(
|
||||
"required", false,
|
||||
"message", "User's IP has already been validated"
|
||||
);
|
||||
}
|
||||
|
||||
return ImmutableMap.of(
|
||||
"required", true,
|
||||
"message", "User has no TOTP exemptions."
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
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.IPUtils;
|
||||
import net.frozenorb.apiv3.utils.TOTPUtils;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class GETUserVerifyPassword 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.getPassword() == null) {
|
||||
return ErrorUtils.invalidInput("User provided does not have password set.");
|
||||
}
|
||||
|
||||
char[] password = req.queryParams("password").toCharArray();
|
||||
boolean authorized = user.checkPassword(password);
|
||||
|
||||
return ImmutableMap.of(
|
||||
"authorized", authorized
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -39,16 +39,16 @@ public final class POSTUserConfirmRegister implements Route {
|
||||
return ErrorUtils.error("Email token is expired");
|
||||
}
|
||||
|
||||
String password = req.queryParams("password");
|
||||
char[] password = req.queryParams("password").toCharArray();
|
||||
|
||||
if (password.length() < 8) {
|
||||
if (password.length < 8) {
|
||||
return ErrorUtils.error("Your password is too short.");
|
||||
} else if (commonPasswords.contains(password)) {
|
||||
} else if (commonPasswords.contains(new String(password))) {
|
||||
return ErrorUtils.error("Your password is too common. Please use a more secure password.");
|
||||
}
|
||||
|
||||
user.setEmailToken(null);
|
||||
user.setPassword(password.toCharArray());
|
||||
user.setPassword(password);
|
||||
APIv3.getDatastore().save(user);
|
||||
|
||||
return ImmutableMap.of(
|
||||
|
@ -19,11 +19,10 @@ import java.util.regex.Pattern;
|
||||
|
||||
public final class POSTUserRegister implements Route {
|
||||
|
||||
private static final Pattern VALID_EMAIL_PATTERN =
|
||||
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
// TODO: POSSIBLE? perms check
|
||||
// TODO: make messages better
|
||||
private static final Pattern VALID_EMAIL_PATTERN = 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.byId(req.params("id"));
|
||||
|
@ -3,11 +3,14 @@ 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.IPUtils;
|
||||
import net.frozenorb.apiv3.utils.TOTPUtils;
|
||||
import spark.Request;
|
||||
import spark.Response;
|
||||
import spark.Route;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public final class POSTUserVerifyTOTP implements Route {
|
||||
|
||||
public Object handle(Request req, Response res) {
|
||||
@ -21,11 +24,37 @@ public final class POSTUserVerifyTOTP implements Route {
|
||||
return ErrorUtils.invalidInput("User provided does not have TOTP code set.");
|
||||
}
|
||||
|
||||
String userIp = req.queryParams("userIp");
|
||||
|
||||
if (!IPUtils.isValidIP(userIp)) {
|
||||
return ErrorUtils.invalidInput("IP address \"" + userIp + "\" is not valid.");
|
||||
}
|
||||
|
||||
int providedCode = Integer.parseInt(req.queryParams("code"));
|
||||
|
||||
if (TOTPUtils.wasRecentlyUsed(user, providedCode)) {
|
||||
return ImmutableMap.of(
|
||||
"verified", TOTPUtils.authorizeUser(user, providedCode)
|
||||
"authorized", false,
|
||||
"message", "TOTP code was recently used."
|
||||
);
|
||||
}
|
||||
|
||||
boolean authorized = TOTPUtils.authorizeUser(user, providedCode);
|
||||
|
||||
if (authorized) {
|
||||
TOTPUtils.markPreAuthorized(user, userIp, 3, TimeUnit.DAYS);
|
||||
TOTPUtils.markRecentlyUsed(user, providedCode);
|
||||
|
||||
return ImmutableMap.of(
|
||||
"authorized", true,
|
||||
"message", "Valid TOTP code provided."
|
||||
);
|
||||
} else {
|
||||
return ImmutableMap.of(
|
||||
"authorized", false,
|
||||
"message", "TOTP code was not valid."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -5,16 +5,25 @@ import com.cribbstechnologies.clients.mandrill.model.MandrillHtmlMessage;
|
||||
import com.cribbstechnologies.clients.mandrill.model.MandrillMessageRequest;
|
||||
import com.cribbstechnologies.clients.mandrill.model.MandrillRecipient;
|
||||
import com.cribbstechnologies.clients.mandrill.request.MandrillMessagesRequest;
|
||||
import com.twilio.sdk.TwilioRestException;
|
||||
import com.twilio.sdk.resource.factory.MessageFactory;
|
||||
import com.twilio.sdk.resource.instance.Message;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
import net.frozenorb.apiv3.models.NotificationTemplate;
|
||||
import net.frozenorb.apiv3.utils.MandrillUtils;
|
||||
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
|
||||
import net.frozenorb.apiv3.utils.TwillioUtils;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.apache.http.message.BasicNameValuePair;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class Notification {
|
||||
|
||||
private static final MandrillMessagesRequest messagesRequest = MandrillUtils.createMessagesRequest();
|
||||
private static final MandrillMessagesRequest mandrillMessagesRequest = MandrillUtils.createMessagesRequest();
|
||||
private static final MessageFactory twillioMessageFactory = TwillioUtils.createMessageFactory();
|
||||
|
||||
private final String subject;
|
||||
private final String body;
|
||||
@ -38,15 +47,24 @@ public final class Notification {
|
||||
try {
|
||||
MandrillMessageRequest request = new MandrillMessageRequest();
|
||||
request.setMessage(message);
|
||||
messagesRequest.sendMessage(request);
|
||||
mandrillMessagesRequest.sendMessage(request);
|
||||
} catch (RequestFailedException ex) {
|
||||
throw new IOException("Failed to send notification to user", ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void sendAsText(String phoneNumber) throws IOException {
|
||||
// TODO
|
||||
throw new IOException(new NotImplementedException());
|
||||
List<NameValuePair> params = new ArrayList<>();
|
||||
|
||||
params.add(new BasicNameValuePair("To", phoneNumber));
|
||||
params.add(new BasicNameValuePair("From", "+13108795180"));
|
||||
params.add(new BasicNameValuePair("Body", body));
|
||||
|
||||
try {
|
||||
twillioMessageFactory.create(params);
|
||||
} catch (TwilioRestException ex) {
|
||||
throw new IOException("Failed to send notification to user", ex);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -10,5 +10,6 @@ public class Permissions {
|
||||
|
||||
public static final String REMOVE_PUNISHMENT = "minehq.punishment.remove"; // minehq.punishment.remove.%TYPE%
|
||||
public static final String CREATE_PUNISHMENT = "minehq.punishment.create"; // minehq.punishment.create.%TYPE%
|
||||
public static final String PROTECTED_PUNISHMENT = "minehq.punishment.protected";
|
||||
|
||||
}
|
22
src/main/java/net/frozenorb/apiv3/utils/TwillioUtils.java
Normal file
22
src/main/java/net/frozenorb/apiv3/utils/TwillioUtils.java
Normal file
@ -0,0 +1,22 @@
|
||||
package net.frozenorb.apiv3.utils;
|
||||
|
||||
import com.twilio.sdk.TwilioRestClient;
|
||||
import com.twilio.sdk.resource.factory.MessageFactory;
|
||||
import com.twilio.sdk.resource.instance.Account;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import net.frozenorb.apiv3.APIv3;
|
||||
|
||||
@UtilityClass
|
||||
public class TwillioUtils {
|
||||
|
||||
public static MessageFactory createMessageFactory() {
|
||||
TwilioRestClient twillioClient = new TwilioRestClient(
|
||||
APIv3.getConfig().getProperty("twillio.accountSID"),
|
||||
APIv3.getConfig().getProperty("twillio.authToken")
|
||||
);
|
||||
|
||||
Account account = twillioClient.getAccount();
|
||||
return account.getMessageFactory();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user