Add PWs #61

Zusammengeführt
Lixfel hat 6 Commits von passwords nach master 2024-01-06 16:32:44 +01:00 zusammengeführt
Nur Änderungen aus Commit 6ac6643162 werden angezeigt - Alle Commits anzeigen

Datei anzeigen

@ -1,349 +1,349 @@
/* /*
* This file is a part of the SteamWar software. * This file is a part of the SteamWar software.
* *
* Copyright (C) 2022 SteamWar.de-Serverteam * Copyright (C) 2022 SteamWar.de-Serverteam
* *
* This program is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* This program is distributed in the hope that it will be useful, * This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details. * GNU Affero General Public License for more details.
* *
* You should have received a copy of the GNU Affero General Public License * You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package de.steamwar.sql; package de.steamwar.sql;
import de.steamwar.sql.internal.*; import de.steamwar.sql.internal.*;
import lombok.Getter; import lombok.Getter;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import javax.crypto.Mac; import javax.crypto.Mac;
import javax.crypto.SecretKeyFactory; import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEKeySpec;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.*; import java.util.*;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class SteamwarUser { public class SteamwarUser {
static { static {
new SqlTypeMapper<>(UUID.class, "CHAR(36)", (rs, identifier) -> UUID.fromString(rs.getString(identifier)), (st, index, value) -> st.setString(index, value.toString())); 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) -> { new SqlTypeMapper<>(Locale.class, "VARCHAR(32)", (rs, identifier) -> {
String l = rs.getString(identifier); String l = rs.getString(identifier);
return l != null ? Locale.forLanguageTag(l) : null; return l != null ? Locale.forLanguageTag(l) : null;
}, (st, index, value) -> st.setString(index, value.toLanguageTag())); }, (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)); 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<SteamwarUser> table = new Table<>(SteamwarUser.class, "UserData"); private static final Table<SteamwarUser> table = new Table<>(SteamwarUser.class, "UserData");
private static final Statement insert = table.insertFields("UUID", "UserName"); private static final Statement insert = table.insertFields("UUID", "UserName");
private static final SelectStatement<SteamwarUser> byID = table.selectFields("id"); private static final SelectStatement<SteamwarUser> byID = table.selectFields("id");
private static final SelectStatement<SteamwarUser> byUUID = table.selectFields("UUID"); private static final SelectStatement<SteamwarUser> byUUID = table.selectFields("UUID");
private static final SelectStatement<SteamwarUser> byName = table.selectFields("UserName"); private static final SelectStatement<SteamwarUser> byName = table.selectFields("UserName");
private static final SelectStatement<SteamwarUser> byDiscord = table.selectFields("DiscordId"); private static final SelectStatement<SteamwarUser> byDiscord = table.selectFields("DiscordId");
private static final SelectStatement<SteamwarUser> byTeam = table.selectFields("Team"); private static final SelectStatement<SteamwarUser> byTeam = table.selectFields("Team");
private static final SelectStatement<SteamwarUser> getServerTeam = new SelectStatement<>(table, "SELECT * FROM UserData WHERE UserGroup != 'Member' AND UserGroup != 'YouTuber'"); private static final SelectStatement<SteamwarUser> 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 updateName = table.update(Table.PRIMARY, "UserName");
private static final Statement updatePassword = table.update(Table.PRIMARY, "Password"); 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 updateLocale = table.update(Table.PRIMARY, "Locale", "ManualLocale");
private static final Statement updateTeam = table.update(Table.PRIMARY, "Team"); private static final Statement updateTeam = table.update(Table.PRIMARY, "Team");
private static final Statement updateLeader = table.update(Table.PRIMARY, "Leader"); private static final Statement updateLeader = table.update(Table.PRIMARY, "Leader");
private static final Statement updateDiscord = table.update(Table.PRIMARY, "DiscordId"); 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 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 Statement getFirstjoin = new Statement("SELECT MIN(StartTime) AS FirstJoin FROM Session WHERE UserID = ?");
private static final Map<Integer, SteamwarUser> usersById = new HashMap<>(); private static final Map<Integer, SteamwarUser> usersById = new HashMap<>();
private static final Map<UUID, SteamwarUser> usersByUUID = new HashMap<>(); private static final Map<UUID, SteamwarUser> usersByUUID = new HashMap<>();
private static final Map<String, SteamwarUser> usersByName = new HashMap<>(); private static final Map<String, SteamwarUser> usersByName = new HashMap<>();
private static final Map<Long, SteamwarUser> usersByDiscord = new HashMap<>(); private static final Map<Long, SteamwarUser> usersByDiscord = new HashMap<>();
public static void clear() { public static void clear() {
usersById.clear(); usersById.clear();
usersByName.clear(); usersByName.clear();
usersByUUID.clear(); usersByUUID.clear();
usersByDiscord.clear(); usersByDiscord.clear();
} }
public static void invalidate(int userId) { public static void invalidate(int userId) {
SteamwarUser user = usersById.remove(userId); SteamwarUser user = usersById.remove(userId);
if (user == null) if (user == null)
return; return;
usersByName.remove(user.getUserName()); usersByName.remove(user.getUserName());
usersByUUID.remove(user.getUUID()); usersByUUID.remove(user.getUUID());
usersByDiscord.remove(user.getDiscordId()); usersByDiscord.remove(user.getDiscordId());
} }
public static SteamwarUser get(String userName){ public static SteamwarUser get(String userName){
SteamwarUser user = usersByName.get(userName.toLowerCase()); SteamwarUser user = usersByName.get(userName.toLowerCase());
if(user != null) if(user != null)
return user; return user;
return byName.select(userName); return byName.select(userName);
} }
public static SteamwarUser get(UUID uuid){ public static SteamwarUser get(UUID uuid){
SteamwarUser user = usersByUUID.get(uuid); SteamwarUser user = usersByUUID.get(uuid);
if(user != null) if(user != null)
return user; return user;
return byUUID.select(uuid); return byUUID.select(uuid);
} }
public static SteamwarUser get(int id) { public static SteamwarUser get(int id) {
SteamwarUser user = usersById.get(id); SteamwarUser user = usersById.get(id);
if(user != null) if(user != null)
return user; return user;
return byID.select(id); return byID.select(id);
} }
public static SteamwarUser get(Long discordId) { public static SteamwarUser get(Long discordId) {
if(usersByDiscord.containsKey(discordId)) if(usersByDiscord.containsKey(discordId))
return usersByDiscord.get(discordId); return usersByDiscord.get(discordId);
return byDiscord.select(discordId); return byDiscord.select(discordId);
} }
public static SteamwarUser getOrCreate(UUID uuid, String name, Consumer<UUID> newPlayer, BiConsumer<String, String> nameUpdate) { public static SteamwarUser getOrCreate(UUID uuid, String name, Consumer<UUID> newPlayer, BiConsumer<String, String> nameUpdate) {
SteamwarUser user = get(uuid); SteamwarUser user = get(uuid);
if (user != null) { if (user != null) {
if (!user.userName.equals(name)) { if (!user.userName.equals(name)) {
updateName.update(name, user.id); updateName.update(name, user.id);
nameUpdate.accept(user.userName, name); nameUpdate.accept(user.userName, name);
user.userName = name; user.userName = name;
} }
return user; return user;
} else { } else {
insert.update(uuid, name); insert.update(uuid, name);
newPlayer.accept(uuid); newPlayer.accept(uuid);
return get(uuid); return get(uuid);
} }
} }
public static List<SteamwarUser> getServerTeam() { public static List<SteamwarUser> getServerTeam() {
return getServerTeam.listSelect(); return getServerTeam.listSelect();
} }
public static List<SteamwarUser> getTeam(int teamId) { public static List<SteamwarUser> getTeam(int teamId) {
return byTeam.listSelect(teamId); return byTeam.listSelect(teamId);
} }
public static void batchCache(Set<Integer> ids) { public static void batchCache(Set<Integer> ids) {
ids.removeIf(usersById::containsKey); ids.removeIf(usersById::containsKey);
if(ids.isEmpty()) if(ids.isEmpty())
return; return;
try (SelectStatement<SteamwarUser> batch = new SelectStatement<>(table, "SELECT * FROM UserData WHERE id IN (" + ids.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")")) { try (SelectStatement<SteamwarUser> batch = new SelectStatement<>(table, "SELECT * FROM UserData WHERE id IN (" + ids.stream().map(Object::toString).collect(Collectors.joining(", ")) + ")")) {
batch.listSelect(); batch.listSelect();
} }
} }
@Getter @Getter
@Field(keys = {Table.PRIMARY}, autoincrement = true) @Field(keys = {Table.PRIMARY}, autoincrement = true)
private final int id; private final int id;
@Field(keys = {"uuid"}) @Field(keys = {"uuid"})
private final UUID uuid; private final UUID uuid;
@Getter @Getter
@Field @Field
private String userName; private String userName;
@Field(nullable = true) @Field(nullable = true)
private String password; private String password;
@Getter @Getter
@Field(def = "0") @Field(def = "0")
private int team; private int team;
@Getter @Getter
@Field(def = "0") @Field(def = "0")
private boolean leader; private boolean leader;
@Field(nullable = true) @Field(nullable = true)
private Locale locale; private Locale locale;
@Field(def = "0") @Field(def = "0")
private boolean manualLocale; private boolean manualLocale;
@Getter @Getter
@Field(keys = {"discordId"}, nullable = true) @Field(keys = {"discordId"}, nullable = true)
private Long discordId; private Long discordId;
private Map<Punishment.PunishmentType, Punishment> punishments = null; private Map<Punishment.PunishmentType, Punishment> punishments = null;
private Set<UserPerm> permissions = null; private Set<UserPerm> permissions = null;
private UserPerm.Prefix prefix = 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) { public SteamwarUser(int id, UUID uuid, String userName, String password, int team, boolean leader, Locale locale, boolean manualLocale, Long discordId) {
this.id = id; this.id = id;
this.uuid = uuid; this.uuid = uuid;
this.userName = userName; this.userName = userName;
this.password = password; this.password = password;
this.team = team; this.team = team;
this.leader = leader; this.leader = leader;
this.locale = locale; this.locale = locale;
this.manualLocale = manualLocale; this.manualLocale = manualLocale;
this.discordId = discordId != null && discordId != 0 ? discordId : null; this.discordId = discordId != null && discordId != 0 ? discordId : null;
usersById.put(id, this); usersById.put(id, this);
usersByName.put(userName.toLowerCase(), this); usersByName.put(userName.toLowerCase(), this);
usersByUUID.put(uuid, this); usersByUUID.put(uuid, this);
if (this.discordId != null) { if (this.discordId != null) {
usersByDiscord.put(discordId, this); usersByDiscord.put(discordId, this);
} }
} }
public UUID getUUID() { public UUID getUUID() {
return uuid; return uuid;
} }
public Locale getLocale() { public Locale getLocale() {
if(locale != null) if(locale != null)
return locale; return locale;
return Locale.getDefault(); return Locale.getDefault();
} }
public Punishment getPunishment(Punishment.PunishmentType type) { public Punishment getPunishment(Punishment.PunishmentType type) {
initPunishments(); initPunishments();
return punishments.getOrDefault(type, null); return punishments.getOrDefault(type, null);
} }
public boolean isPunished(Punishment.PunishmentType punishment) { public boolean isPunished(Punishment.PunishmentType punishment) {
initPunishments(); initPunishments();
if (!punishments.containsKey(punishment)) { if (!punishments.containsKey(punishment)) {
return false; return false;
} }
if (!punishments.get(punishment).isCurrent()) { if (!punishments.get(punishment).isCurrent()) {
if (punishment == Punishment.PunishmentType.Ban) { if (punishment == Punishment.PunishmentType.Ban) {
BannedUserIPs.unbanIPs(id); BannedUserIPs.unbanIPs(id);
} }
punishments.remove(punishment); punishments.remove(punishment);
return false; return false;
} }
return true; return true;
} }
public boolean hasPerm(UserPerm perm) { public boolean hasPerm(UserPerm perm) {
initPerms(); initPerms();
return permissions.contains(perm); return permissions.contains(perm);
} }
public Set<UserPerm> perms() { public Set<UserPerm> perms() {
initPerms(); initPerms();
return permissions; return permissions;
} }
public UserPerm.Prefix prefix() { public UserPerm.Prefix prefix() {
initPerms(); initPerms();
return prefix; return prefix;
} }
public double getOnlinetime() { public double getOnlinetime() {
return getPlaytime.select(rs -> { return getPlaytime.select(rs -> {
if (rs.next() && rs.getBigDecimal("Playtime") != null) if (rs.next() && rs.getBigDecimal("Playtime") != null)
return rs.getBigDecimal("Playtime").doubleValue(); return rs.getBigDecimal("Playtime").doubleValue();
return 0.0; return 0.0;
}, id); }, id);
} }
public Timestamp getFirstjoin() { public Timestamp getFirstjoin() {
return getFirstjoin.select(rs -> { return getFirstjoin.select(rs -> {
if (rs.next()) if (rs.next())
return rs.getTimestamp("FirstJoin"); return rs.getTimestamp("FirstJoin");
return null; return null;
}, id); }, id);
} }
public void punish(Punishment.PunishmentType punishment, Timestamp time, String banReason, int from, boolean perma) { public void punish(Punishment.PunishmentType punishment, Timestamp time, String banReason, int from, boolean perma) {
initPunishments(); initPunishments();
punishments.remove(punishment); punishments.remove(punishment);
punishments.put(punishment, Punishment.createPunishment(id, from, punishment, banReason, time, perma)); punishments.put(punishment, Punishment.createPunishment(id, from, punishment, banReason, time, perma));
} }
public void setTeam(int team) { public void setTeam(int team) {
this.team = team; this.team = team;
updateTeam.update(team, id); updateTeam.update(team, id);
setLeader(false); setLeader(false);
} }
public void setLeader(boolean leader) { public void setLeader(boolean leader) {
this.leader = leader; this.leader = leader;
updateLeader.update(leader, id); updateLeader.update(leader, id);
} }
public void setLocale(Locale locale, boolean manualLocale) { public void setLocale(Locale locale, boolean manualLocale) {
if (locale == null || (this.manualLocale && !manualLocale)) if (locale == null || (this.manualLocale && !manualLocale))
return; return;
this.locale = locale; this.locale = locale;
this.manualLocale = manualLocale; this.manualLocale = manualLocale;
updateLocale.update(locale.toLanguageTag(), manualLocale, id); updateLocale.update(locale.toLanguageTag(), manualLocale, id);
} }
public void setDiscordId(Long discordId) { public void setDiscordId(Long discordId) {
usersByDiscord.remove(this.discordId); usersByDiscord.remove(this.discordId);
this.discordId = discordId; this.discordId = discordId;
updateDiscord.update(discordId, id); updateDiscord.update(discordId, id);
if (discordId != null) { if (discordId != null) {
usersByDiscord.put(discordId, this); usersByDiscord.put(discordId, this);
} }
} }
@SneakyThrows @SneakyThrows
public void setPassword(String password) { public void setPassword(String password) {
SecureRandom random = new SecureRandom(); SecureRandom random = new SecureRandom();
byte[] salt = new byte[16]; byte[] salt = new byte[16];
random.nextBytes(salt); random.nextBytes(salt);
String saltString = Base64.getEncoder().encodeToString(salt); String saltString = Base64.getEncoder().encodeToString(salt);
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 512);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
byte[] hash = factory.generateSecret(spec).getEncoded(); byte[] hash = factory.generateSecret(spec).getEncoded();
String hashString = Base64.getEncoder().encodeToString(hash); String hashString = Base64.getEncoder().encodeToString(hash);
this.password = hashString + ":" + saltString; this.password = hashString + ":" + saltString;
updatePassword.update(this.password, id); updatePassword.update(this.password, id);
} }
@SneakyThrows @SneakyThrows
public boolean verifyPassword(String password) { public boolean verifyPassword(String password) {
if (this.password == null) { if (this.password == null) {
return false; return false;
} }
String[] parts = this.password.split(":"); String[] parts = this.password.split(":");
if (parts.length != 2) { if (parts.length != 2) {
SQLConfig.impl.getLogger().log(Level.SEVERE ,"Invalid password hash for user {0} ({1})", new Object[]{userName, id}); SQLConfig.impl.getLogger().log(Level.SEVERE ,"Invalid password hash for user {0} ({1})", new Object[]{userName, id});
return false; return false;
} }
String hashString = parts[0]; String hashString = parts[0];
byte[] realHash = Base64.getDecoder().decode(hashString); byte[] realHash = Base64.getDecoder().decode(hashString);
String saltString = parts[1]; String saltString = parts[1];
byte[] salt = Base64.getDecoder().decode(saltString); byte[] salt = Base64.getDecoder().decode(saltString);
PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 128); PBEKeySpec spec = new PBEKeySpec(password.toCharArray(), salt, 65536, 512);
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512"); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
byte[] hash = factory.generateSecret(spec).getEncoded(); byte[] hash = factory.generateSecret(spec).getEncoded();
return Arrays.equals(realHash, hash); return Arrays.equals(realHash, hash);
} }
private void initPunishments() { private void initPunishments() {
if(punishments != null) if(punishments != null)
return; return;
punishments = Punishment.getPunishmentsOfPlayer(id); punishments = Punishment.getPunishmentsOfPlayer(id);
} }
private void initPerms() { private void initPerms() {
if(permissions != null) if(permissions != null)
return; return;
permissions = UserPerm.getPerms(id); permissions = UserPerm.getPerms(id);
prefix = permissions.stream().filter(UserPerm.prefixes::containsKey).findAny().map(UserPerm.prefixes::get).orElse(UserPerm.emptyPrefix); prefix = permissions.stream().filter(UserPerm.prefixes::containsKey).findAny().map(UserPerm.prefixes::get).orElse(UserPerm.emptyPrefix);
} }
} }