diff --git a/src/main/java/net/frozenorb/apiv3/APIv3.java b/src/main/java/net/frozenorb/apiv3/APIv3.java index 8cb70c3..0673b16 100644 --- a/src/main/java/net/frozenorb/apiv3/APIv3.java +++ b/src/main/java/net/frozenorb/apiv3/APIv3.java @@ -314,8 +314,8 @@ public final class APIv3 extends AbstractVerticle { http.get("/staff").blockingHandler(new GETStaff(), false); http.get("/users/:id").handler(new GETUsersId()); + http.get("/users/:id/compoundedPermissions").handler(new GETUsersIdCompoundedPermissions()); http.get("/users/:id/details").blockingHandler(new GETUsersIdDetails(), false); - http.get("/users/:id/permissions").handler(new GETUsersIdPermissions()); http.get("/users/:id/requiresTotp").handler(new GETUsersIdRequiresTotp()); http.get("/users/:id/verifyPassword").blockingHandler(new GETUsersIdVerifyPassword(), false); http.post("/users/:id/changePassword").blockingHandler(new POSTUsersIdChangePassword(), false); @@ -343,7 +343,7 @@ public final class APIv3 extends AbstractVerticle { ctx.response().setStatusCode(code); if (!ctx.request().path().contains("dumps")) { - //log.info(gson.toJson(response)); + log.info(gson.toJson(response)); } ctx.response().end(gson.toJson(response)); diff --git a/src/main/java/net/frozenorb/apiv3/model/User.java b/src/main/java/net/frozenorb/apiv3/model/User.java index 3ce90b5..e79158b 100644 --- a/src/main/java/net/frozenorb/apiv3/model/User.java +++ b/src/main/java/net/frozenorb/apiv3/model/User.java @@ -7,6 +7,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.hash.Hashing; +import com.google.common.primitives.Ints; import com.mongodb.async.SingleResultCallback; import com.mongodb.async.client.MongoCollection; import com.mongodb.client.result.UpdateResult; @@ -34,6 +35,7 @@ import java.time.Instant; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; @Entity @AllArgsConstructor @@ -250,10 +252,12 @@ public final class User { } Map result = new HashMap<>(); + List ranks = getRanksScoped(serverGroup, grants); result.put("user", this); result.put("access", accessInfo); - result.put("rank", getHighestRankScoped(serverGroup, grants).getId()); + result.put("bestRank", ranks.get(0).getId()); + result.put("ranks", ranks.stream().map(Rank::getId).collect(Collectors.toList())); result.put("totpSetup", getTotpSecret() != null); if (activeMute != null) { @@ -472,7 +476,7 @@ public final class User { } public void hasPermissionAnywhere(String permission, SingleResultCallback callback) { - getGlobalPermissions((permissions, error) -> { + getCompoundedPermissions((permissions, error) -> { if (error != null) { callback.onResult(null, error); } else { @@ -482,58 +486,79 @@ public final class User { }); } - public void getGlobalPermissions(SingleResultCallback> callback) { + public void getCompoundedPermissions(SingleResultCallback> callback) { Grant.findByUser(this, (grants, error) -> { if (error != null) { callback.onResult(null, error); } else { - Map globalPermissions = PermissionUtils.getDefaultPermissions(getHighestRankScoped(null, grants)); - - for (Map.Entry serverGroupEntry : getHighestRanks(grants).entrySet()) { - ServerGroup serverGroup = serverGroupEntry.getKey(); - Rank rank = serverGroupEntry.getValue(); + Map globalPermissions = new HashMap<>(); + for (Rank globalRank : getRanksScoped(null, grants)) { globalPermissions = PermissionUtils.mergePermissions( globalPermissions, - serverGroup.calculateScopedPermissions(rank) + PermissionUtils.getDefaultPermissions(globalRank) ); } + for (ServerGroup serverGroup : ServerGroup.findAll()) { + for (Rank scopedRank : getRanksScoped(serverGroup, grants)) { + globalPermissions = PermissionUtils.mergePermissions( + globalPermissions, + serverGroup.calculateScopedPermissions(scopedRank) + ); + } + } + callback.onResult(ImmutableMap.copyOf(globalPermissions), null); } }); } - private Map getHighestRanks(List grants) { - Map highestRanks = new HashMap<>(); - - for (ServerGroup serverGroup : ServerGroup.findAll()) { - highestRanks.put(serverGroup, getHighestRankScoped(serverGroup, grants)); - } - - return highestRanks; - } - - private Rank getHighestRankScoped(ServerGroup serverGroup, Iterable grants) { - Rank highest = null; + private List getRanksScoped(ServerGroup serverGroup, Iterable grants) { + List grantedRanks = new LinkedList<>(); for (Grant grant : grants) { if (!grant.isActive() || (serverGroup != null && !grant.appliesOn(serverGroup))) { continue; } - Rank rank = Rank.findById(grant.getRank()); + Rank grantedRank = Rank.findById(grant.getRank()); + grantedRanks.add(grantedRank); + } - if (highest == null || rank.getWeight() > highest.getWeight()) { - highest = rank; + if (grantedRanks.isEmpty()) { + grantedRanks.add(Rank.findById("default")); + return grantedRanks; + } + + Iterator iterator = grantedRanks.iterator(); + + // This is to remove redundant ranks. Say they have mod, mod-plus, admin, and youtuber, + // we should remove mod and mod-plus as it'll be made redundant by the higher ranked admin. + while (iterator.hasNext()) { + Rank rank = iterator.next(); + + // Check all other ranks for inherited collision + for (Rank otherRank : grantedRanks) { + if (otherRank == rank) { + continue; + } + + Rank parent = otherRank; + + // Iterate up the inheritance tree to detect rank redundancies. + while (parent.getInheritsFromId() != null) { + if (parent == rank) { + iterator.remove(); + } + + parent = Rank.findById(otherRank.getInheritsFromId()); + } } } - if (highest != null) { - return highest; - } else { - return Rank.findById("default"); - } + grantedRanks.sort((a, b) -> Ints.compare(b.getWeight(), a.getWeight())); + return grantedRanks; } public void insert(SingleResultCallback callback) { diff --git a/src/main/java/net/frozenorb/apiv3/route/users/GETUsersIdPermissions.java b/src/main/java/net/frozenorb/apiv3/route/users/GETUsersIdCompoundedPermissions.java similarity index 84% rename from src/main/java/net/frozenorb/apiv3/route/users/GETUsersIdPermissions.java rename to src/main/java/net/frozenorb/apiv3/route/users/GETUsersIdCompoundedPermissions.java index 143ded5..dc0bb53 100644 --- a/src/main/java/net/frozenorb/apiv3/route/users/GETUsersIdPermissions.java +++ b/src/main/java/net/frozenorb/apiv3/route/users/GETUsersIdCompoundedPermissions.java @@ -6,7 +6,7 @@ import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.model.User; import net.frozenorb.apiv3.util.ErrorUtils; -public final class GETUsersIdPermissions implements Handler { +public final class GETUsersIdCompoundedPermissions implements Handler { public void handle(RoutingContext ctx) { User.findById(ctx.request().getParam("id"), (user, error) -> { @@ -15,7 +15,7 @@ public final class GETUsersIdPermissions implements Handler { } else if (user == null) { ErrorUtils.respondNotFound(ctx, "User", ctx.request().getParam("id")); } else { - user.getGlobalPermissions((permissions, error2) -> { + user.getCompoundedPermissions((permissions, error2) -> { if (error2 != null) { ErrorUtils.respondInternalError(ctx, error2); } else {