package net.frozenorb.apiv3.models; import com.google.common.base.Charsets; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.hash.Hashing; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import net.frozenorb.apiv3.APIv3; import net.frozenorb.apiv3.serialization.ExcludeFromReplies; import net.frozenorb.apiv3.utils.MojangUtils; import net.frozenorb.apiv3.utils.PermissionUtils; import net.frozenorb.apiv3.utils.UUIDUtils; import org.bson.Document; import org.mongodb.morphia.annotations.Entity; import org.mongodb.morphia.annotations.Id; import org.mongodb.morphia.annotations.Indexed; import java.util.*; @Entity(value = "users", noClassnameStored = true) @AllArgsConstructor public final class User { @Getter @Id private UUID id; @Getter @Indexed private String lastUsername; @Getter @ExcludeFromReplies private Map aliases = new HashMap<>(); @Getter @Setter @ExcludeFromReplies private String totpSecret; @Getter @Indexed @ExcludeFromReplies @Setter private String emailToken; @Getter @ExcludeFromReplies @Setter private Date emailTokenSetAt; @Getter @ExcludeFromReplies private String password; @Getter @Setter private String email; @Getter private String phoneNumber; @Getter private String lastSeenOn; @Getter private Date lastSeenAt; @Getter private Date firstSeenAt; @Getter private boolean online; public static User byId(String id) { try { return byId(UUID.fromString(id)); } catch (Exception ex) { return null; } } public static User byId(UUID id) { if (UUIDUtils.isAcceptableUUID(id)) { return APIv3.getDatastore().createQuery(User.class).field("id").equal(id).get(); } else { return null; } } public static Map byIdGrouped(Iterable ids) { Map result = new HashMap<>(); Set uuidsToSearch = new HashSet<>(); for (UUID id : ids) { result.put(id, null); if (UUIDUtils.isAcceptableUUID(id)) { uuidsToSearch.add(id); } } APIv3.getDatastore().createQuery(User.class).field("id").in(uuidsToSearch).forEach((user) -> { result.put(user.getId(), user); }); return result; } 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 lastUsername) { this.id = id; this.lastUsername = ""; // Intentional, so updateUsername actually does something. this.aliases = new HashMap<>(); this.totpSecret = null; this.password = null; this.email = null; this.phoneNumber = null; this.lastSeenOn = null; this.lastSeenAt = new Date(); this.firstSeenAt = new Date(); updateUsername(lastUsername); } public boolean hasPermissionScoped(String permission, ServerGroup scope) { Rank highestRank = getHighestRank(scope); Map scopedPermissions = PermissionUtils.mergePermissions( PermissionUtils.getDefaultPermissions(highestRank), scope.calculatePermissions(highestRank) ); return scopedPermissions.containsKey(permission) && scopedPermissions.get(permission); } public boolean hasPermissionAnywhere(String permission) { Map globalPermissions = PermissionUtils.getDefaultPermissions(getHighestRank()); for (Map.Entry 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 getGrants() { return APIv3.getDatastore().createQuery(Grant.class).field("user").equal(id).asList(); } public List getIPLog() { 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("userIp").equal(ip).get(); if (existing == null) { existing = new IPLogEntry(this, ip); APIv3.getDatastore().save(existing); } return existing; } public List getPunishments() { return APIv3.getDatastore().createQuery(Punishment.class).field("user").equal(id).asList(); } public List getPunishments(Iterable types) { return APIv3.getDatastore().createQuery(Punishment.class).field("user").equal(id).field("type").in(types).asList(); } public UserMetaEntry getMeta(ServerGroup group) { return APIv3.getDatastore().createQuery(UserMetaEntry.class).field("user").equal(id).field("serverGroup").equal(group.getId()).get(); } public void saveMeta(ServerGroup group, Document data) { UserMetaEntry entry = getMeta(group); if (entry == null) { APIv3.getDatastore().save(new UserMetaEntry(this, group, data)); } else { entry.setData(data); APIv3.getDatastore().save(entry); } } public boolean seenOnServer(Server server) { if (online && server.getId().equals(this.lastSeenOn)) { return false; } this.lastSeenOn = server.getId(); if (!online) { this.lastSeenAt = new Date(); } this.online = true; return true; } public void leftServer() { this.lastSeenAt = new Date(); this.online = false; } public void updateUsername(String username) { if (!username.equals(lastUsername)) { this.lastUsername = username; User withNewUsername; while ((withNewUsername = User.byLastUsername(username)) != null) { String newUsername = MojangUtils.getName(withNewUsername.getId()); withNewUsername.updateUsername(newUsername); } } this.aliases.put(username, new Date()); APIv3.getDatastore().save(this); } public void setPassword(String input) { this.password = Hashing .sha256() .hashString(input + "$" + id.toString(), Charsets.UTF_8) .toString(); } public boolean checkPassword(String input) { String hashed = Hashing .sha256() .hashString(input + "$" + id.toString(), Charsets.UTF_8) .toString(); return hashed.equals(password); } public Rank getHighestRank() { return getHighestRank(null); } public Rank getHighestRank(ServerGroup serverGroup) { return getHighestRank(serverGroup, getGrants()); } // This is only used to help batch requests to mongo public Rank getHighestRank(ServerGroup serverGroup, Iterable grants) { Rank highest = null; for (Grant grant : grants) { if (!grant.isActive() || (serverGroup != null && !grant.appliesOn(serverGroup))) { continue; } Rank rank = Rank.byId(grant.getRank()); if (highest == null || rank.getWeight() > highest.getWeight()) { highest = rank; } } if (highest != null) { return highest; } else { return Rank.byId("default"); } } public Map getHighestRanks() { Map highestRanks = new HashMap<>(); Rank defaultRank = Rank.byId("default"); List 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 getLoginInfo(Server server) { return getLoginInfo( server, getPunishments(ImmutableSet.of( Punishment.PunishmentType.BLACKLIST, Punishment.PunishmentType.BAN, Punishment.PunishmentType.MUTE )), getGrants() ); } // This is only used to help batch requests to mongo public Map getLoginInfo(Server server, Iterable punishments, Iterable grants) { Punishment activeMute = null; String accessDenialReason = null; for (Punishment punishment : punishments) { if (!punishment.isActive()) { continue; } if (punishment.getType() == Punishment.PunishmentType.MUTE) { activeMute = punishment; } else { accessDenialReason = punishment.getAccessDenialReason(); } } Rank highestRank = getHighestRank(ServerGroup.byId(server.getServerGroup()), grants); // Generics are weird, yes we have to do this. ImmutableMap.Builder result = ImmutableMap.builder() .put("user", this) .put("access", ImmutableMap.of( "allowed", accessDenialReason == null, "message", accessDenialReason == null ? "Public server" : accessDenialReason )) .put("rank", highestRank.getId()) .put("totpSetup", getTotpSecret() != null); if (activeMute != null) { result.put("mute", activeMute); } return result.build(); } }