From ea52162ffe33cbf05943dd58faa79c5ce0f68d21 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sun, 17 Dec 2023 16:34:08 +0100 Subject: [PATCH 1/5] Updates --- src/de/steamwar/sql/SteamwarUser.java | 56 ++++++++++++++++++++++++++- src/de/steamwar/sql/Token.java | 27 +++++++++++++ 2 files changed, 81 insertions(+), 2 deletions(-) diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index edbcb26..40f9ca9 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -21,11 +21,18 @@ package de.steamwar.sql; import de.steamwar.sql.internal.*; import lombok.Getter; +import lombok.SneakyThrows; +import javax.crypto.Mac; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.security.MessageDigest; +import java.security.SecureRandom; import java.sql.Timestamp; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; +import java.util.logging.Level; import java.util.stream.Collectors; public class SteamwarUser { @@ -49,6 +56,7 @@ public class SteamwarUser { private static final SelectStatement getServerTeam = new SelectStatement<>(table, "SELECT * FROM UserData WHERE UserGroup != 'Member' AND UserGroup != 'YouTuber'"); private static final Statement updateName = table.update(Table.PRIMARY, "UserName"); + private static final Statement updatePassword = table.update(Table.PRIMARY, "Password"); private static final Statement updateLocale = table.update(Table.PRIMARY, "Locale", "ManualLocale"); private static final Statement updateTeam = table.update(Table.PRIMARY, "Team"); private static final Statement updateLeader = table.update(Table.PRIMARY, "Leader"); @@ -148,7 +156,9 @@ public class SteamwarUser { @Getter @Field private String userName; - @Getter + @Field(nullable = true) + private String password; + @Getter @Field(def = "0") private int team; @Getter @@ -166,10 +176,11 @@ public class SteamwarUser { private Set permissions = null; private UserPerm.Prefix prefix = null; - public SteamwarUser(int id, UUID uuid, String userName, int team, boolean leader, Locale locale, boolean manualLocale, Long discordId) { + public SteamwarUser(int id, UUID uuid, String userName, String password, int team, boolean leader, Locale locale, boolean manualLocale, Long discordId) { this.id = id; this.uuid = uuid; this.userName = userName; + this.password = password; this.team = team; this.leader = leader; this.locale = locale; @@ -280,6 +291,47 @@ public class SteamwarUser { } } + @SneakyThrows + public void setPassword(String password) { + SecureRandom random = new SecureRandom(); + byte[] salt = new byte[16]; + random.nextBytes(salt); + String saltString = Base64.getEncoder().encodeToString(salt); + + PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); + byte[] hash = factory.generateSecret(spec).getEncoded(); + + String hashString = Base64.getEncoder().encodeToString(hash); + + this.password = hashString + ":" + saltString; + updatePassword.update(this.password, id); + } + + @SneakyThrows + public boolean verifyPassword(String password) { + if (this.password == null) { + return false; + } + + String[] parts = this.password.split(":"); + if (parts.length != 2) { + SQLConfig.impl.getLogger().log(Level.SEVERE ,"Invalid password hash for user {0} ({1})", new Object[]{userName, id}); + return false; + } + + String hashString = parts[0]; + byte[] realHash = Base64.getDecoder().decode(hashString); + String saltString = parts[1]; + byte[] salt = Base64.getDecoder().decode(saltString); + + PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); + byte[] hash = factory.generateSecret(spec).getEncoded(); + + return Arrays.equals(realHash, hash); + } + private void initPunishments() { if(punishments != null) return; diff --git a/src/de/steamwar/sql/Token.java b/src/de/steamwar/sql/Token.java index db34967..007a16e 100644 --- a/src/de/steamwar/sql/Token.java +++ b/src/de/steamwar/sql/Token.java @@ -25,9 +25,13 @@ import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.SneakyThrows; import lombok.ToString; +import java.security.MessageDigest; +import java.security.SecureRandom; import java.sql.Timestamp; +import java.util.Base64; import java.util.List; @AllArgsConstructor @@ -41,6 +45,29 @@ public class Token { private static final SelectStatement getHash = table.selectFields("hash"); private static final Statement delete = table.delete(Table.PRIMARY); + @SneakyThrows + private static String getHash(String code) { + return Base64.getEncoder().encodeToString(MessageDigest.getInstance("SHA-512").digest(code.getBytes())); + } + + @SneakyThrows + public static String createToken(String name, SteamwarUser owner) { + SecureRandom random = new SecureRandom(); + byte[] bytes = new byte[20]; + random.nextBytes(bytes); + + String code = Base64.getEncoder().encodeToString(bytes); + + String hash = getHash(code); + create(name, owner, hash); + return code; + } + + public static Token getTokenByCode(String code) { + String hash = getHash(code); + return get(hash); + } + public static Token create(String name, SteamwarUser owner, String hash) { int id = insert.insertGetKey(name, owner, hash); return get(id); From 6ac6643162412b498c738306957a77732301d532 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sun, 17 Dec 2023 17:22:47 +0100 Subject: [PATCH 2/5] src/de/steamwar/sql/SteamwarUser.java aktualisiert --- src/de/steamwar/sql/SteamwarUser.java | 698 +++++++++++++------------- 1 file changed, 349 insertions(+), 349 deletions(-) diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index 40f9ca9..8e05174 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -1,349 +1,349 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2022 SteamWar.de-Serverteam - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package de.steamwar.sql; - -import de.steamwar.sql.internal.*; -import lombok.Getter; -import lombok.SneakyThrows; - -import javax.crypto.Mac; -import javax.crypto.SecretKeyFactory; -import javax.crypto.spec.PBEKeySpec; -import java.security.MessageDigest; -import java.security.SecureRandom; -import java.sql.Timestamp; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.stream.Collectors; - -public class SteamwarUser { - - static { - new SqlTypeMapper<>(UUID.class, "CHAR(36)", (rs, identifier) -> UUID.fromString(rs.getString(identifier)), (st, index, value) -> st.setString(index, value.toString())); - new SqlTypeMapper<>(Locale.class, "VARCHAR(32)", (rs, identifier) -> { - String l = rs.getString(identifier); - return l != null ? Locale.forLanguageTag(l) : null; - }, (st, index, value) -> st.setString(index, value.toLanguageTag())); - new SqlTypeMapper<>(SteamwarUser.class, null, (rs, identifier) -> { throw new SecurityException("SteamwarUser cannot be used as type (recursive select)"); }, (st, index, value) -> st.setInt(index, value.id)); - } - - private static final Table table = new Table<>(SteamwarUser.class, "UserData"); - private static final Statement insert = table.insertFields("UUID", "UserName"); - private static final SelectStatement byID = table.selectFields("id"); - private static final SelectStatement byUUID = table.selectFields("UUID"); - private static final SelectStatement byName = table.selectFields("UserName"); - private static final SelectStatement byDiscord = table.selectFields("DiscordId"); - private static final SelectStatement byTeam = table.selectFields("Team"); - private static final SelectStatement getServerTeam = new SelectStatement<>(table, "SELECT * FROM UserData WHERE UserGroup != 'Member' AND UserGroup != 'YouTuber'"); - - private static final Statement updateName = table.update(Table.PRIMARY, "UserName"); - private static final Statement updatePassword = table.update(Table.PRIMARY, "Password"); - private static final Statement updateLocale = table.update(Table.PRIMARY, "Locale", "ManualLocale"); - private static final Statement updateTeam = table.update(Table.PRIMARY, "Team"); - private static final Statement updateLeader = table.update(Table.PRIMARY, "Leader"); - private static final Statement updateDiscord = table.update(Table.PRIMARY, "DiscordId"); - - private static final Statement getPlaytime = new Statement("SELECT SUM(UNIX_TIMESTAMP(EndTime) - UNIX_TIMESTAMP(StartTime)) as Playtime FROM Session WHERE UserID = ?"); - private static final Statement getFirstjoin = new Statement("SELECT MIN(StartTime) AS FirstJoin FROM Session WHERE UserID = ?"); - - private static final Map usersById = new HashMap<>(); - private static final Map usersByUUID = new HashMap<>(); - private static final Map usersByName = new HashMap<>(); - private static final Map usersByDiscord = new HashMap<>(); - public static void clear() { - usersById.clear(); - usersByName.clear(); - usersByUUID.clear(); - usersByDiscord.clear(); - } - - public static void invalidate(int userId) { - SteamwarUser user = usersById.remove(userId); - if (user == null) - return; - usersByName.remove(user.getUserName()); - usersByUUID.remove(user.getUUID()); - usersByDiscord.remove(user.getDiscordId()); - } - - public static SteamwarUser get(String userName){ - SteamwarUser user = usersByName.get(userName.toLowerCase()); - if(user != null) - return user; - return byName.select(userName); - } - - public static SteamwarUser get(UUID uuid){ - SteamwarUser user = usersByUUID.get(uuid); - if(user != null) - return user; - return byUUID.select(uuid); - } - - public static SteamwarUser get(int id) { - SteamwarUser user = usersById.get(id); - if(user != null) - return user; - return byID.select(id); - } - - public static SteamwarUser get(Long discordId) { - if(usersByDiscord.containsKey(discordId)) - return usersByDiscord.get(discordId); - return byDiscord.select(discordId); - } - - public static SteamwarUser getOrCreate(UUID uuid, String name, Consumer newPlayer, BiConsumer nameUpdate) { - SteamwarUser user = get(uuid); - - if (user != null) { - if (!user.userName.equals(name)) { - updateName.update(name, user.id); - nameUpdate.accept(user.userName, name); - user.userName = name; - } - - return user; - } else { - insert.update(uuid, name); - newPlayer.accept(uuid); - return get(uuid); - } - } - - public static List getServerTeam() { - return getServerTeam.listSelect(); - } - - public static List getTeam(int teamId) { - return byTeam.listSelect(teamId); - } - - public static void batchCache(Set ids) { - ids.removeIf(usersById::containsKey); - if(ids.isEmpty()) - return; - - try (SelectStatement batch = new SelectStatement<>(table, "SELECT * FROM UserData WHERE id IN (" + ids.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")")) { - batch.listSelect(); - } - } - - @Getter - @Field(keys = {Table.PRIMARY}, autoincrement = true) - private final int id; - @Field(keys = {"uuid"}) - private final UUID uuid; - @Getter - @Field - private String userName; - @Field(nullable = true) - private String password; - @Getter - @Field(def = "0") - private int team; - @Getter - @Field(def = "0") - private boolean leader; - @Field(nullable = true) - private Locale locale; - @Field(def = "0") - private boolean manualLocale; - @Getter - @Field(keys = {"discordId"}, nullable = true) - private Long discordId; - - private Map punishments = null; - private Set permissions = null; - private UserPerm.Prefix prefix = null; - - public SteamwarUser(int id, UUID uuid, String userName, String password, int team, boolean leader, Locale locale, boolean manualLocale, Long discordId) { - this.id = id; - this.uuid = uuid; - this.userName = userName; - this.password = password; - this.team = team; - this.leader = leader; - this.locale = locale; - this.manualLocale = manualLocale; - this.discordId = discordId != null && discordId != 0 ? discordId : null; - - usersById.put(id, this); - usersByName.put(userName.toLowerCase(), this); - usersByUUID.put(uuid, this); - if (this.discordId != null) { - usersByDiscord.put(discordId, this); - } - } - - public UUID getUUID() { - return uuid; - } - - public Locale getLocale() { - if(locale != null) - return locale; - return Locale.getDefault(); - } - - public Punishment getPunishment(Punishment.PunishmentType type) { - initPunishments(); - return punishments.getOrDefault(type, null); - } - - public boolean isPunished(Punishment.PunishmentType punishment) { - initPunishments(); - if (!punishments.containsKey(punishment)) { - return false; - } - if (!punishments.get(punishment).isCurrent()) { - if (punishment == Punishment.PunishmentType.Ban) { - BannedUserIPs.unbanIPs(id); - } - punishments.remove(punishment); - return false; - } - return true; - } - - public boolean hasPerm(UserPerm perm) { - initPerms(); - return permissions.contains(perm); - } - - public Set perms() { - initPerms(); - return permissions; - } - - public UserPerm.Prefix prefix() { - initPerms(); - return prefix; - } - - public double getOnlinetime() { - return getPlaytime.select(rs -> { - if (rs.next() && rs.getBigDecimal("Playtime") != null) - return rs.getBigDecimal("Playtime").doubleValue(); - return 0.0; - }, id); - } - - public Timestamp getFirstjoin() { - return getFirstjoin.select(rs -> { - if (rs.next()) - return rs.getTimestamp("FirstJoin"); - return null; - }, id); - } - - public void punish(Punishment.PunishmentType punishment, Timestamp time, String banReason, int from, boolean perma) { - initPunishments(); - punishments.remove(punishment); - punishments.put(punishment, Punishment.createPunishment(id, from, punishment, banReason, time, perma)); - } - - public void setTeam(int team) { - this.team = team; - updateTeam.update(team, id); - setLeader(false); - } - - public void setLeader(boolean leader) { - this.leader = leader; - updateLeader.update(leader, id); - } - - public void setLocale(Locale locale, boolean manualLocale) { - if (locale == null || (this.manualLocale && !manualLocale)) - return; - - this.locale = locale; - this.manualLocale = manualLocale; - updateLocale.update(locale.toLanguageTag(), manualLocale, id); - } - - public void setDiscordId(Long discordId) { - usersByDiscord.remove(this.discordId); - this.discordId = discordId; - updateDiscord.update(discordId, id); - if (discordId != null) { - usersByDiscord.put(discordId, this); - } - } - - @SneakyThrows - public void setPassword(String password) { - SecureRandom random = new SecureRandom(); - byte[] salt = new byte[16]; - random.nextBytes(salt); - String saltString = Base64.getEncoder().encodeToString(salt); - - PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); - byte[] hash = factory.generateSecret(spec).getEncoded(); - - String hashString = Base64.getEncoder().encodeToString(hash); - - this.password = hashString + ":" + saltString; - updatePassword.update(this.password, id); - } - - @SneakyThrows - public boolean verifyPassword(String password) { - if (this.password == null) { - return false; - } - - String[] parts = this.password.split(":"); - if (parts.length != 2) { - SQLConfig.impl.getLogger().log(Level.SEVERE ,"Invalid password hash for user {0} ({1})", new Object[]{userName, id}); - return false; - } - - String hashString = parts[0]; - byte[] realHash = Base64.getDecoder().decode(hashString); - String saltString = parts[1]; - byte[] salt = Base64.getDecoder().decode(saltString); - - PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); - byte[] hash = factory.generateSecret(spec).getEncoded(); - - return Arrays.equals(realHash, hash); - } - - private void initPunishments() { - if(punishments != null) - return; - - punishments = Punishment.getPunishmentsOfPlayer(id); - } - - private void initPerms() { - if(permissions != null) - return; - - permissions = UserPerm.getPerms(id); - prefix = permissions.stream().filter(UserPerm.prefixes::containsKey).findAny().map(UserPerm.prefixes::get).orElse(UserPerm.emptyPrefix); - } -} +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2022 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.sql; + +import de.steamwar.sql.internal.*; +import lombok.Getter; +import lombok.SneakyThrows; + +import javax.crypto.Mac; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.PBEKeySpec; +import java.security.MessageDigest; +import java.security.SecureRandom; +import java.sql.Timestamp; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.stream.Collectors; + +public class SteamwarUser { + + static { + new SqlTypeMapper<>(UUID.class, "CHAR(36)", (rs, identifier) -> UUID.fromString(rs.getString(identifier)), (st, index, value) -> st.setString(index, value.toString())); + new SqlTypeMapper<>(Locale.class, "VARCHAR(32)", (rs, identifier) -> { + String l = rs.getString(identifier); + return l != null ? Locale.forLanguageTag(l) : null; + }, (st, index, value) -> st.setString(index, value.toLanguageTag())); + new SqlTypeMapper<>(SteamwarUser.class, null, (rs, identifier) -> { throw new SecurityException("SteamwarUser cannot be used as type (recursive select)"); }, (st, index, value) -> st.setInt(index, value.id)); + } + + private static final Table table = new Table<>(SteamwarUser.class, "UserData"); + private static final Statement insert = table.insertFields("UUID", "UserName"); + private static final SelectStatement byID = table.selectFields("id"); + private static final SelectStatement byUUID = table.selectFields("UUID"); + private static final SelectStatement byName = table.selectFields("UserName"); + private static final SelectStatement byDiscord = table.selectFields("DiscordId"); + private static final SelectStatement byTeam = table.selectFields("Team"); + private static final SelectStatement getServerTeam = new SelectStatement<>(table, "SELECT * FROM UserData WHERE UserGroup != 'Member' AND UserGroup != 'YouTuber'"); + + private static final Statement updateName = table.update(Table.PRIMARY, "UserName"); + private static final Statement updatePassword = table.update(Table.PRIMARY, "Password"); + private static final Statement updateLocale = table.update(Table.PRIMARY, "Locale", "ManualLocale"); + private static final Statement updateTeam = table.update(Table.PRIMARY, "Team"); + private static final Statement updateLeader = table.update(Table.PRIMARY, "Leader"); + private static final Statement updateDiscord = table.update(Table.PRIMARY, "DiscordId"); + + private static final Statement getPlaytime = new Statement("SELECT SUM(UNIX_TIMESTAMP(EndTime) - UNIX_TIMESTAMP(StartTime)) as Playtime FROM Session WHERE UserID = ?"); + private static final Statement getFirstjoin = new Statement("SELECT MIN(StartTime) AS FirstJoin FROM Session WHERE UserID = ?"); + + private static final Map usersById = new HashMap<>(); + private static final Map usersByUUID = new HashMap<>(); + private static final Map usersByName = new HashMap<>(); + private static final Map usersByDiscord = new HashMap<>(); + public static void clear() { + usersById.clear(); + usersByName.clear(); + usersByUUID.clear(); + usersByDiscord.clear(); + } + + public static void invalidate(int userId) { + SteamwarUser user = usersById.remove(userId); + if (user == null) + return; + usersByName.remove(user.getUserName()); + usersByUUID.remove(user.getUUID()); + usersByDiscord.remove(user.getDiscordId()); + } + + public static SteamwarUser get(String userName){ + SteamwarUser user = usersByName.get(userName.toLowerCase()); + if(user != null) + return user; + return byName.select(userName); + } + + public static SteamwarUser get(UUID uuid){ + SteamwarUser user = usersByUUID.get(uuid); + if(user != null) + return user; + return byUUID.select(uuid); + } + + public static SteamwarUser get(int id) { + SteamwarUser user = usersById.get(id); + if(user != null) + return user; + return byID.select(id); + } + + public static SteamwarUser get(Long discordId) { + if(usersByDiscord.containsKey(discordId)) + return usersByDiscord.get(discordId); + return byDiscord.select(discordId); + } + + public static SteamwarUser getOrCreate(UUID uuid, String name, Consumer newPlayer, BiConsumer nameUpdate) { + SteamwarUser user = get(uuid); + + if (user != null) { + if (!user.userName.equals(name)) { + updateName.update(name, user.id); + nameUpdate.accept(user.userName, name); + user.userName = name; + } + + return user; + } else { + insert.update(uuid, name); + newPlayer.accept(uuid); + return get(uuid); + } + } + + public static List getServerTeam() { + return getServerTeam.listSelect(); + } + + public static List getTeam(int teamId) { + return byTeam.listSelect(teamId); + } + + public static void batchCache(Set ids) { + ids.removeIf(usersById::containsKey); + if(ids.isEmpty()) + return; + + try (SelectStatement batch = new SelectStatement<>(table, "SELECT * FROM UserData WHERE id IN (" + ids.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")")) { + batch.listSelect(); + } + } + + @Getter + @Field(keys = {Table.PRIMARY}, autoincrement = true) + private final int id; + @Field(keys = {"uuid"}) + private final UUID uuid; + @Getter + @Field + private String userName; + @Field(nullable = true) + private String password; + @Getter + @Field(def = "0") + private int team; + @Getter + @Field(def = "0") + private boolean leader; + @Field(nullable = true) + private Locale locale; + @Field(def = "0") + private boolean manualLocale; + @Getter + @Field(keys = {"discordId"}, nullable = true) + private Long discordId; + + private Map punishments = null; + private Set permissions = null; + private UserPerm.Prefix prefix = null; + + public SteamwarUser(int id, UUID uuid, String userName, String password, int team, boolean leader, Locale locale, boolean manualLocale, Long discordId) { + this.id = id; + this.uuid = uuid; + this.userName = userName; + this.password = password; + this.team = team; + this.leader = leader; + this.locale = locale; + this.manualLocale = manualLocale; + this.discordId = discordId != null && discordId != 0 ? discordId : null; + + usersById.put(id, this); + usersByName.put(userName.toLowerCase(), this); + usersByUUID.put(uuid, this); + if (this.discordId != null) { + usersByDiscord.put(discordId, this); + } + } + + public UUID getUUID() { + return uuid; + } + + public Locale getLocale() { + if(locale != null) + return locale; + return Locale.getDefault(); + } + + public Punishment getPunishment(Punishment.PunishmentType type) { + initPunishments(); + return punishments.getOrDefault(type, null); + } + + public boolean isPunished(Punishment.PunishmentType punishment) { + initPunishments(); + if (!punishments.containsKey(punishment)) { + return false; + } + if (!punishments.get(punishment).isCurrent()) { + if (punishment == Punishment.PunishmentType.Ban) { + BannedUserIPs.unbanIPs(id); + } + punishments.remove(punishment); + return false; + } + return true; + } + + public boolean hasPerm(UserPerm perm) { + initPerms(); + return permissions.contains(perm); + } + + public Set perms() { + initPerms(); + return permissions; + } + + public UserPerm.Prefix prefix() { + initPerms(); + return prefix; + } + + public double getOnlinetime() { + return getPlaytime.select(rs -> { + if (rs.next() && rs.getBigDecimal("Playtime") != null) + return rs.getBigDecimal("Playtime").doubleValue(); + return 0.0; + }, id); + } + + public Timestamp getFirstjoin() { + return getFirstjoin.select(rs -> { + if (rs.next()) + return rs.getTimestamp("FirstJoin"); + return null; + }, id); + } + + public void punish(Punishment.PunishmentType punishment, Timestamp time, String banReason, int from, boolean perma) { + initPunishments(); + punishments.remove(punishment); + punishments.put(punishment, Punishment.createPunishment(id, from, punishment, banReason, time, perma)); + } + + public void setTeam(int team) { + this.team = team; + updateTeam.update(team, id); + setLeader(false); + } + + public void setLeader(boolean leader) { + this.leader = leader; + updateLeader.update(leader, id); + } + + public void setLocale(Locale locale, boolean manualLocale) { + if (locale == null || (this.manualLocale && !manualLocale)) + return; + + this.locale = locale; + this.manualLocale = manualLocale; + updateLocale.update(locale.toLanguageTag(), manualLocale, id); + } + + public void setDiscordId(Long discordId) { + usersByDiscord.remove(this.discordId); + this.discordId = discordId; + updateDiscord.update(discordId, id); + if (discordId != null) { + usersByDiscord.put(discordId, this); + } + } + + @SneakyThrows + public void setPassword(String password) { + SecureRandom random = new SecureRandom(); + byte[] salt = new byte[16]; + random.nextBytes(salt); + String saltString = Base64.getEncoder().encodeToString(salt); + + PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 512); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); + byte[] hash = factory.generateSecret(spec).getEncoded(); + + String hashString = Base64.getEncoder().encodeToString(hash); + + this.password = hashString + ":" + saltString; + updatePassword.update(this.password, id); + } + + @SneakyThrows + public boolean verifyPassword(String password) { + if (this.password == null) { + return false; + } + + String[] parts = this.password.split(":"); + if (parts.length != 2) { + SQLConfig.impl.getLogger().log(Level.SEVERE ,"Invalid password hash for user {0} ({1})", new Object[]{userName, id}); + return false; + } + + String hashString = parts[0]; + byte[] realHash = Base64.getDecoder().decode(hashString); + String saltString = parts[1]; + byte[] salt = Base64.getDecoder().decode(saltString); + + PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 512); + SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); + byte[] hash = factory.generateSecret(spec).getEncoded(); + + return Arrays.equals(realHash, hash); + } + + private void initPunishments() { + if(punishments != null) + return; + + punishments = Punishment.getPunishmentsOfPlayer(id); + } + + private void initPerms() { + if(permissions != null) + return; + + permissions = UserPerm.getPerms(id); + prefix = permissions.stream().filter(UserPerm.prefixes::containsKey).findAny().map(UserPerm.prefixes::get).orElse(UserPerm.emptyPrefix); + } +} From e83ab4762d77567a01b0b71bdf12b65bf2aca3bd Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Sun, 17 Dec 2023 18:28:52 +0100 Subject: [PATCH 3/5] WepPW Command --- src/de/steamwar/sql/SteamwarUser.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index 40f9ca9..47f4786 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -112,13 +112,12 @@ public class SteamwarUser { return byDiscord.select(discordId); } - public static SteamwarUser getOrCreate(UUID uuid, String name, Consumer newPlayer, BiConsumer nameUpdate) { + public static SteamwarUser getOrCreate(UUID uuid, String name, Consumer newPlayer) { SteamwarUser user = get(uuid); if (user != null) { if (!user.userName.equals(name)) { updateName.update(name, user.id); - nameUpdate.accept(user.userName, name); user.userName = name; } From efd7f27c60114586e5ccd1ccbd9c1ec32c0272b9 Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Tue, 19 Dec 2023 20:39:09 +0100 Subject: [PATCH 4/5] Unused Imports and Random --- src/de/steamwar/sql/SteamwarUser.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index 421af33..514e428 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -23,19 +23,17 @@ import de.steamwar.sql.internal.*; import lombok.Getter; import lombok.SneakyThrows; -import javax.crypto.Mac; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; -import java.security.MessageDigest; import java.security.SecureRandom; import java.sql.Timestamp; import java.util.*; -import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.logging.Level; import java.util.stream.Collectors; public class SteamwarUser { + private static final SecureRandom random = new SecureRandom(); static { new SqlTypeMapper<>(UUID.class, "CHAR(36)", (rs, identifier) -> UUID.fromString(rs.getString(identifier)), (st, index, value) -> st.setString(index, value.toString())); @@ -292,7 +290,6 @@ public class SteamwarUser { @SneakyThrows public void setPassword(String password) { - SecureRandom random = new SecureRandom(); byte[] salt = new byte[16]; random.nextBytes(salt); String saltString = Base64.getEncoder().encodeToString(salt); From 9a3630b07cb870266669ba120985a2c0a8ebbf5c Mon Sep 17 00:00:00 2001 From: Chaoscaot Date: Tue, 19 Dec 2023 20:49:37 +0100 Subject: [PATCH 5/5] Code Review Stuff --- src/de/steamwar/sql/SteamwarUser.java | 77 ++++++++++++++++----------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index 514e428..3166124 100644 --- a/src/de/steamwar/sql/SteamwarUser.java +++ b/src/de/steamwar/sql/SteamwarUser.java @@ -25,18 +25,28 @@ import lombok.SneakyThrows; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.spec.InvalidKeySpecException; import java.sql.Timestamp; import java.util.*; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.logging.Level; import java.util.stream.Collectors; public class SteamwarUser { private static final SecureRandom random = new SecureRandom(); + private static final SecretKeyFactory factory; static { - new SqlTypeMapper<>(UUID.class, "CHAR(36)", (rs, identifier) -> UUID.fromString(rs.getString(identifier)), (st, index, value) -> st.setString(index, value.toString())); + try { + factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); + } catch (NoSuchAlgorithmException e) { + throw new SecurityException(e); + } + + new SqlTypeMapper<>(UUID.class, "CHAR(36)", (rs, identifier) -> UUID.fromString(rs.getString(identifier)), (st, index, value) -> st.setString(index, value.toString())); new SqlTypeMapper<>(Locale.class, "VARCHAR(32)", (rs, identifier) -> { String l = rs.getString(identifier); return l != null ? Locale.forLanguageTag(l) : null; @@ -110,12 +120,13 @@ public class SteamwarUser { return byDiscord.select(discordId); } - public static SteamwarUser getOrCreate(UUID uuid, String name, Consumer newPlayer) { + public static SteamwarUser getOrCreate(UUID uuid, String name, Consumer newPlayer, BiConsumer nameUpdate) { SteamwarUser user = get(uuid); if (user != null) { if (!user.userName.equals(name)) { updateName.update(name, user.id); + nameUpdate.accept(user.userName, name); user.userName = name; } @@ -288,44 +299,50 @@ public class SteamwarUser { } } - @SneakyThrows public void setPassword(String password) { - byte[] salt = new byte[16]; - random.nextBytes(salt); - String saltString = Base64.getEncoder().encodeToString(salt); + try { + byte[] salt = new byte[16]; + random.nextBytes(salt); + String saltString = Base64.getEncoder().encodeToString(salt); - PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 512); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); - byte[] hash = factory.generateSecret(spec).getEncoded(); - - String hashString = Base64.getEncoder().encodeToString(hash); - - this.password = hashString + ":" + saltString; - updatePassword.update(this.password, id); + byte[] hash = generateHash(password, salt); + String hashString = Base64.getEncoder().encodeToString(hash); + this.password = hashString + ":" + saltString; + updatePassword.update(this.password, id); + } catch (Exception e) { + throw new SecurityException(e); + } } - @SneakyThrows public boolean verifyPassword(String password) { - if (this.password == null) { + try { + if (this.password == null) { + return false; + } + + String[] parts = this.password.split(":"); + if (parts.length != 2) { + SQLConfig.impl.getLogger().log(Level.SEVERE ,"Invalid password hash for user {0} ({1})", new Object[]{userName, id}); + return false; + } + + String hashString = parts[0]; + byte[] realHash = Base64.getDecoder().decode(hashString); + String saltString = parts[1]; + byte[] salt = Base64.getDecoder().decode(saltString); + byte[] hash = generateHash(password, salt); + return Arrays.equals(realHash, hash); + } catch (Exception e) { + SQLConfig.impl.getLogger().log(Level.SEVERE, "Error while verifying password for user " + userName + " (" + id + ")", e); return false; } + } - String[] parts = this.password.split(":"); - if (parts.length != 2) { - SQLConfig.impl.getLogger().log(Level.SEVERE ,"Invalid password hash for user {0} ({1})", new Object[]{userName, id}); - return false; - } - - String hashString = parts[0]; - byte[] realHash = Base64.getDecoder().decode(hashString); - String saltString = parts[1]; - byte[] salt = Base64.getDecoder().decode(saltString); - + private byte[] generateHash(String password, byte[] salt) + throws InvalidKeySpecException { PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 512); - SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); - byte[] hash = factory.generateSecret(spec).getEncoded(); + return factory.generateSecret(spec).getEncoded(); - return Arrays.equals(realHash, hash); } private void initPunishments() {