diff --git a/build.gradle b/build.gradle index d5f000a..88a7743 100644 --- a/build.gradle +++ b/build.gradle @@ -44,8 +44,8 @@ ext { compileJava.options.encoding = 'UTF-8' -sourceCompatibility = 1.8 -targetCompatibility = 1.8 +sourceCompatibility = 17 +targetCompatibility = 17 sourceSets { main { diff --git a/src/de/steamwar/sql/Fight.java b/src/de/steamwar/sql/Fight.java index efa230d..f543be4 100644 --- a/src/de/steamwar/sql/Fight.java +++ b/src/de/steamwar/sql/Fight.java @@ -46,7 +46,6 @@ public class Fight { fight.initPlayers(fightPlayers); } - SteamwarUser.batchCache(fightPlayers.stream().map(FightPlayer::getUserID).collect(Collectors.toSet())); return fights; } diff --git a/src/de/steamwar/sql/NodeDownload.java b/src/de/steamwar/sql/NodeDownload.java index 666cf68..54d81b5 100644 --- a/src/de/steamwar/sql/NodeDownload.java +++ b/src/de/steamwar/sql/NodeDownload.java @@ -20,9 +20,11 @@ package de.steamwar.sql; import de.steamwar.sql.internal.Field; +import de.steamwar.sql.internal.SelectStatement; import de.steamwar.sql.internal.Statement; import de.steamwar.sql.internal.Table; import lombok.AllArgsConstructor; +import lombok.Getter; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -30,13 +32,13 @@ import java.sql.Timestamp; import java.time.Instant; @AllArgsConstructor +@Getter public class NodeDownload { - - private static final char[] HEX = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; - private static final String LINK_BASE = "https://steamwar.de/download.php?schem="; - private static final Table table = new Table<>(NodeDownload.class); private static final Statement insert = table.insertFields("NodeId", "Link"); + private static final SelectStatement select = table.selectFields("Link"); + private static final SelectStatement getId = table.select(Table.PRIMARY); + private static final Statement delete = table.delete(Table.PRIMARY); @Field(keys = {Table.PRIMARY}) private final int nodeId; @@ -45,25 +47,20 @@ public class NodeDownload { @Field(def = "CURRENT_TIMESTAMP") private final Timestamp timestamp; - public static String getLink(SchematicNode schem){ - if(schem.isDir()) - throw new SecurityException("Can not Download Directorys"); - MessageDigest digest; - try { - digest = MessageDigest.getInstance("SHA-1"); - } catch (NoSuchAlgorithmException e) { - throw new SecurityException(e); - } - digest.reset(); - digest.update((Instant.now().toString() + schem.getOwner() + schem.getId()).getBytes()); - String hash = base16encode(digest.digest()); - insert.update(schem.getId(), hash); - return LINK_BASE + hash; + public static NodeDownload addCode(SchematicNode node, String link) { + insert.update(node.getId(), link); + return get(node.getId()); } - public static String base16encode(byte[] byteArray) { - StringBuilder hexBuffer = new StringBuilder(byteArray.length * 2); - for (byte b : byteArray) - hexBuffer.append(HEX[(b >>> 4) & 0xF]).append(HEX[b & 0xF]); - return hexBuffer.toString(); + + public static NodeDownload get(int nodeId) { + return getId.select(nodeId); + } + + public static NodeDownload get(String link) { + return select.select(link); + } + + public void delete() { + delete.update(nodeId); } } diff --git a/src/de/steamwar/sql/SchematicNode.java b/src/de/steamwar/sql/SchematicNode.java index 6aa83e1..b535a56 100644 --- a/src/de/steamwar/sql/SchematicNode.java +++ b/src/de/steamwar/sql/SchematicNode.java @@ -383,8 +383,8 @@ public class SchematicNode { } public int getRank() { - if(isDir()) - throw new SecurityException("Node is Directory"); + if (isDir()) + return 0; return nodeRank; } @@ -400,8 +400,8 @@ public class SchematicNode { } public SchematicType getSchemtype() { - if(isDir()) - throw new SecurityException("Is Directory"); + if (isDir()) + return null; return nodeType; } @@ -443,8 +443,25 @@ public class SchematicNode { return SchemElo.getElo(this, season); } - public boolean accessibleByUser(int user) { - return NodeMember.getNodeMember(nodeId, user) != null; + public boolean accessibleByUser(SteamwarUser user) { + if (user.getId() == nodeOwner) { + return true; + } + + if (parentNode == null) { + return NodeMember.getNodeMember(nodeId, user.getId()) != null; + } + + SchematicNode parent = getParentNode(); + while (parent != null) { + NodeMember member = NodeMember.getNodeMember(nodeId, user.getId()); + if (member != null || (parent.getOwner() == user.getId() && parent.parentNode == null)) { + return true; + } + parent = parent.getParentNode(); + } + + return false; } public Set getMembers() { @@ -506,6 +523,19 @@ public class SchematicNode { return builder.toString(); } + public List> generateBreadcrumbsMap(SteamwarUser user) { + List> map = new ArrayList<>(); + Optional currentNode = Optional.of(this); + if(currentNode.map(SchematicNode::isDir).orElse(false)) { + map.add(Map.entry(getName(), getId())); + } + while (currentNode.isPresent()) { + currentNode = currentNode.flatMap(schematicNode -> Optional.ofNullable(NodeMember.getNodeMember(schematicNode.getId(), effectiveOwner)).map(NodeMember::getParent).orElse(schematicNode.getOptionalParent())).map(SchematicNode::getSchematicNode); + currentNode.ifPresent(node -> map.add(0, Map.entry(node.getName(), node.getId()))); + } + return map; + } + private static final List FORBIDDEN_NAMES = Collections.unmodifiableList(Arrays.asList("public")); public static boolean invalidSchemName(String[] layers) { for (String layer : layers) { diff --git a/src/de/steamwar/sql/SchematicType.java b/src/de/steamwar/sql/SchematicType.java index 43cfb44..0ee483a 100644 --- a/src/de/steamwar/sql/SchematicType.java +++ b/src/de/steamwar/sql/SchematicType.java @@ -26,7 +26,7 @@ import java.util.*; public class SchematicType { - public static final SchematicType Normal = new SchematicType("Normal", "", Type.NORMAL, null, "STONE_BUTTON"); + public static final SchematicType Normal = new SchematicType("Normal", "", Type.NORMAL, null, "STONE_BUTTON", false); private static final Map fromDB; private static final List types; @@ -60,25 +60,31 @@ public class SchematicType { private final String material; @Getter private final Date deadline; + @Getter + private final boolean manualCheck; - SchematicType(String name, String kuerzel, Type type, SchematicType checkType, String material){ - this(name, kuerzel, type, checkType, material, null); + SchematicType(String name, String kuerzel, Type type, SchematicType checkType, String material, boolean manualCheck){ + this(name, kuerzel, type, checkType, material, null, manualCheck); } - SchematicType(String name, String kuerzel, Type type, SchematicType checkType, String material, Date deadline){ + SchematicType(String name, String kuerzel, Type type, SchematicType checkType, String material, Date deadline, boolean manualCheck){ this.name = name; this.kuerzel = kuerzel; this.type = type; this.checkType = checkType; this.material = material; this.deadline = deadline; + this.manualCheck = manualCheck; } public boolean isAssignable(){ - return type == Type.NORMAL || (type == Type.FIGHT_TYPE && checkType != null); + return type == Type.NORMAL || (type == Type.FIGHT_TYPE && checkType != null) || !manualCheck; } public SchematicType checkType(){ + if (!manualCheck) { + return this; + } return checkType; } diff --git a/src/de/steamwar/sql/SteamwarUser.java b/src/de/steamwar/sql/SteamwarUser.java index 3a9cfa5..48375a5 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 { @@ -36,7 +43,6 @@ public class SteamwarUser { String l = rs.getString(identifier); return l != null ? Locale.forLanguageTag(l) : null; }, (st, index, value) -> st.setString(index, value.toLanguageTag())); - SqlTypeMapper.nameEnumMapper(UserGroup.class); 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)); } @@ -51,6 +57,7 @@ public class SteamwarUser { private static final SelectStatement getAll = new SelectStatement<>(table, "SELECT * FROM UserData"); 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"); @@ -59,50 +66,19 @@ public class SteamwarUser { 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); } @@ -136,16 +112,6 @@ public class SteamwarUser { 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; @@ -154,11 +120,9 @@ public class SteamwarUser { @Getter @Field private String userName; - @Deprecated - @Getter - @Field(def = "'Member'") - private final UserGroup userGroup; - @Getter + @Field(nullable = true) + private String password; + @Getter @Field(def = "0") private int team; @Getter @@ -176,23 +140,16 @@ public class SteamwarUser { private Set permissions = null; private UserPerm.Prefix prefix = null; - public SteamwarUser(int id, UUID uuid, String userName, UserGroup userGroup, 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.userGroup = userGroup; + 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() { @@ -283,14 +240,51 @@ public class SteamwarUser { } 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; diff --git a/src/de/steamwar/sql/Token.java b/src/de/steamwar/sql/Token.java new file mode 100644 index 0000000..007a16e --- /dev/null +++ b/src/de/steamwar/sql/Token.java @@ -0,0 +1,110 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2023 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.Field; +import de.steamwar.sql.internal.SelectStatement; +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 +@Getter +@ToString +public class Token { + private static final Table table = new Table<>(Token.class); + private static final Statement insert = table.insertFields(true, "Name", "Owner", "Hash"); + private static final SelectStatement get = table.select(Table.PRIMARY); + private static final SelectStatement listUser = table.selectFields("owner"); + 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); + } + + public static Token get(int id) { + return get.select(id); + } + + public static List listUser(SteamwarUser owner) { + return listUser.listSelect(owner); + } + + public static Token get(String hash) { + return getHash.select(hash); + } + + public static void delete(Token id) { + delete.update(id.getId()); + } + + @Field(keys = Table.PRIMARY, autoincrement = true) + private final int id; + @Field(keys = "NameOwner") + private final String name; + @Field(keys = "NameOwner") + private final int owner; + @Field + private final Timestamp created; + @Field + private final String hash; + + public void delete() { + delete(this); + } + + public SteamwarUser getOwner() { + return SteamwarUser.get(owner); + } +} diff --git a/src/de/steamwar/sql/Tutorial.java b/src/de/steamwar/sql/Tutorial.java index 9febcba..9d5c762 100644 --- a/src/de/steamwar/sql/Tutorial.java +++ b/src/de/steamwar/sql/Tutorial.java @@ -44,7 +44,6 @@ public class Tutorial { public static List getPage(int page, int elementsPerPage, boolean released) { List tutorials = by_popularity.listSelect(released, page * elementsPerPage, elementsPerPage); - SteamwarUser.batchCache(tutorials.stream().map(tutorial -> tutorial.creator).collect(Collectors.toSet())); return tutorials; } diff --git a/src/de/steamwar/sql/UserGroup.java b/src/de/steamwar/sql/UserGroup.java deleted file mode 100644 index 6a2b1ad..0000000 --- a/src/de/steamwar/sql/UserGroup.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * This file is a part of the SteamWar software. - * - * Copyright (C) 2023 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 lombok.AllArgsConstructor; -import lombok.Getter; - -@Deprecated -@AllArgsConstructor -public enum UserGroup { - Admin("§4", "§e", "Admin", true, true, true, true), - Developer("§3", "§f", "Dev", true, true, true, true), - Moderator("§c", "§f", "Mod", true, true, true, true), - Supporter("§9", "§f", "Sup", false, true, true, true), - Builder("§2", "§f", "Arch", false, true, false, true), - YouTuber("§5", "§f", "YT", false, false, false, true), - Member("§7", "§7", "", false, false, false, false); - - @Getter - private final String colorCode; - @Getter - private final String chatColorCode; - @Getter - private final String chatPrefix; - @Getter - private final boolean adminGroup; - @Getter - private final boolean teamGroup; - @Getter - private final boolean checkSchematics; - @Getter - private final boolean privilegedMods; - - public static UserGroup getUsergroup(String name) { - for(UserGroup group : values()) { - if(group.name().equalsIgnoreCase(name)) - return group; - } - - throw new IllegalArgumentException(name); - } -} \ No newline at end of file diff --git a/src/de/steamwar/sql/UserPerm.java b/src/de/steamwar/sql/UserPerm.java index 80a63dd..7b78b36 100644 --- a/src/de/steamwar/sql/UserPerm.java +++ b/src/de/steamwar/sql/UserPerm.java @@ -51,14 +51,14 @@ public enum UserPerm { Map p = new EnumMap<>(UserPerm.class); emptyPrefix = new Prefix("§7", ""); p.put(PREFIX_NONE, emptyPrefix); - p.put(PREFIX_YOUTUBER, new Prefix("§5", "YT")); - p.put(PREFIX_GUIDE, new Prefix("§f", "Guide")); + p.put(PREFIX_YOUTUBER, new Prefix("§7", "YT")); + p.put(PREFIX_GUIDE, new Prefix("§a", "Guide")); - p.put(PREFIX_BUILDER, new Prefix("§2", "Arch")); - p.put(PREFIX_SUPPORTER, new Prefix("§9", "Sup")); + p.put(PREFIX_SUPPORTER, new Prefix("§6", "Sup")); p.put(PREFIX_MODERATOR, new Prefix("§6", "Mod")); - p.put(PREFIX_DEVELOPER, new Prefix("§b", "Dev")); - p.put(PREFIX_ADMIN, new Prefix("§4", "Admin")); + p.put(PREFIX_BUILDER, new Prefix("§e", "Arch")); + p.put(PREFIX_DEVELOPER, new Prefix("§e", "Dev")); + p.put(PREFIX_ADMIN, new Prefix("§e", "Admin")); prefixes = Collections.unmodifiableMap(p); } @@ -82,8 +82,11 @@ public enum UserPerm { @Getter @AllArgsConstructor public static class Prefix { + private static int ordinalCounter = 0; + private final String colorCode; private final String chatPrefix; + private final int ordinal = ordinalCounter++; } @AllArgsConstructor