Stops migration from altering encrypted books
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
All checks were successful
EpicKnarvik97/Books-Without-Borders/pipeline/head This commit looks good
This commit is contained in:
@@ -46,8 +46,6 @@ Books without Borders has got your back!
|
||||
- The `/migrateBooks` command allows for easy fixing of old book naming, changing the title author separator (the
|
||||
default changed from `,` to `¤`, as a comma is a natural character to use in a title), or updating books saved as txt
|
||||
to yml.
|
||||
- Note that if real encryption is enabled, migrating the books will store them unencrypted (like they used to before
|
||||
real encryption was implemented) afterward.
|
||||
|
||||
### Book formatting
|
||||
|
||||
|
@@ -307,6 +307,11 @@ public enum Translatable implements TranslatableMessage {
|
||||
* The error displayed when attempting to change a book's title with a title that's too long
|
||||
*/
|
||||
ERROR_TITLE_LENGTH,
|
||||
|
||||
/**
|
||||
* The error displayed when attempting to encrypt a book that has an existing encrypted file
|
||||
*/
|
||||
ERROR_ENCRYPT_ALREADY_SAVED,
|
||||
;
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,23 @@
|
||||
package net.knarcraft.bookswithoutborders.container;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.encryption.AESConfiguration;
|
||||
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
|
||||
import org.bukkit.inventory.meta.BookMeta;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A representation of an encrypted book
|
||||
*
|
||||
* @param bookMeta <p>The book's book meta</p>
|
||||
* @param encryptionStyle <p>The book's encryption style</p>
|
||||
* @param encryptionKey <p>The book's encryption key, or null if admin decrypt is forbidden</p>
|
||||
* @param data <p>The encrypted pages of the book</p>
|
||||
* @param aesConfiguration <p>The AES configuration for the book, or null if not AES encrypted</p>
|
||||
*/
|
||||
public record EncryptedBook(@NotNull BookMeta bookMeta, @NotNull EncryptionStyle encryptionStyle,
|
||||
@NotNull String encryptionKey, @NotNull List<String> data,
|
||||
@Nullable AESConfiguration aesConfiguration) {
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package net.knarcraft.bookswithoutborders.thread;
|
||||
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.container.EncryptedBook;
|
||||
import net.knarcraft.bookswithoutborders.container.MigrationRequest;
|
||||
import net.knarcraft.bookswithoutborders.utility.BookFileHelper;
|
||||
import net.knarcraft.bookswithoutborders.utility.BookHelper;
|
||||
@@ -89,12 +90,17 @@ public class MigrationQueueThread implements Runnable {
|
||||
if (bookMeta == null) {
|
||||
return false;
|
||||
}
|
||||
String slash = BooksWithoutBorders.getConfiguration().getSlash();
|
||||
|
||||
BookMeta loadedBook;
|
||||
EncryptedBook encryptedBook = null;
|
||||
BookMeta loadedBook = null;
|
||||
String extension = BookFileHelper.getExtensionFromPath(file.getName());
|
||||
if (extension.equalsIgnoreCase("yml")) {
|
||||
loadedBook = BookToFromTextHelper.encryptedBookFromYml(file, bookMeta, "", true);
|
||||
} else if (extension.equalsIgnoreCase("txt")) {
|
||||
if (file.getAbsolutePath().contains("Books" + slash + "Encrypted")) {
|
||||
encryptedBook = BookToFromTextHelper.encryptedBookFromYml(file, bookMeta, "", true);
|
||||
if (encryptedBook != null) {
|
||||
loadedBook = encryptedBook.bookMeta();
|
||||
}
|
||||
} else if (extension.equalsIgnoreCase("txt") || extension.equalsIgnoreCase("yml")) {
|
||||
loadedBook = BookToFromTextHelper.bookFromFile(file, bookMeta);
|
||||
} else {
|
||||
BooksWithoutBorders.log(Level.WARNING, "File with unexpected extension " + extension + " encountered!");
|
||||
@@ -102,7 +108,7 @@ public class MigrationQueueThread implements Runnable {
|
||||
}
|
||||
|
||||
if (loadedBook == null) {
|
||||
BooksWithoutBorders.log(Level.SEVERE, "Unable to load book: " + file.getName());
|
||||
BooksWithoutBorders.log(Level.SEVERE, "Unable to load book: " + file.getAbsolutePath());
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -125,8 +131,13 @@ public class MigrationQueueThread implements Runnable {
|
||||
newName = "[" + key + "]" + newName;
|
||||
}
|
||||
|
||||
return saveBook(file.getParentFile(), newName, loadedBook, file);
|
||||
} catch (IllegalArgumentException exception) {
|
||||
if (encryptedBook != null) {
|
||||
BookToFromTextHelper.encryptedBookToYml(file.getParentFile().getAbsolutePath(), newName, encryptedBook);
|
||||
} else {
|
||||
BookToFromTextHelper.bookToYml(file.getParentFile().getAbsolutePath(), newName, bookMeta);
|
||||
}
|
||||
return deleteBook(file.getParentFile(), newName, file);
|
||||
} catch (IllegalArgumentException | IOException exception) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Failed to migrate book: " + file.getName() + " Cause:");
|
||||
BooksWithoutBorders.sendErrorMessage(player, exception.getMessage());
|
||||
return false;
|
||||
@@ -134,25 +145,19 @@ public class MigrationQueueThread implements Runnable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a migrated book
|
||||
* Deletes a migrated book, if the file path changed
|
||||
*
|
||||
* @param parent <p>The parent folder the file belongs to</p>
|
||||
* @param newName <p>The new name of the file</p>
|
||||
* @param bookMeta <p>The metadata of the book to migrate</p>
|
||||
* @param oldFile <p>The old file path, in case it should be deleted</p>
|
||||
* @param parent <p>The parent folder the file belongs to</p>
|
||||
* @param newName <p>The new name of the file</p>
|
||||
* @param oldFile <p>The old file path, in case it should be deleted</p>
|
||||
* @return <p>True if successfully saved</p>
|
||||
*/
|
||||
private boolean saveBook(@NotNull File parent, @NotNull String newName, @NotNull BookMeta bookMeta,
|
||||
@NotNull File oldFile) {
|
||||
try {
|
||||
BookToFromTextHelper.bookToYml(parent.getAbsolutePath(), newName, bookMeta);
|
||||
if (!oldFile.getAbsolutePath().equalsIgnoreCase(new File(parent, newName + ".yml").getAbsolutePath())) {
|
||||
return oldFile.delete();
|
||||
}
|
||||
private boolean deleteBook(@NotNull File parent, @NotNull String newName,
|
||||
@NotNull File oldFile) {
|
||||
if (!oldFile.getAbsolutePath().equalsIgnoreCase(new File(parent, newName + ".yml").getAbsolutePath())) {
|
||||
return oldFile.delete();
|
||||
} else {
|
||||
return true;
|
||||
} catch (IOException exception) {
|
||||
BooksWithoutBorders.log(Level.SEVERE, "Failed to save migrated book: " + newName);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -198,6 +198,8 @@ public final class BookFileHelper {
|
||||
String stripped = stripExtensionFromPath(path);
|
||||
if (stripped.contains(separator)) {
|
||||
return stripped.split(separator)[0];
|
||||
} else if (stripped.contains(",")) {
|
||||
return stripped.split(",")[0];
|
||||
} else {
|
||||
return stripped;
|
||||
}
|
||||
@@ -215,6 +217,8 @@ public final class BookFileHelper {
|
||||
String stripped = stripExtensionFromPath(path);
|
||||
if (stripped.contains(separator)) {
|
||||
return stripped.split(separator)[1];
|
||||
} else if (stripped.contains(",")) {
|
||||
return stripped.split(",")[1];
|
||||
} else {
|
||||
return BooksWithoutBorders.getStringFormatter().getUnFormattedColoredMessage(Formatting.NEUTRAL_UNKNOWN_AUTHOR);
|
||||
}
|
||||
|
@@ -140,7 +140,6 @@ public final class BookLoader {
|
||||
BookHelper.increaseGeneration(book);
|
||||
book.setAmount(numCopies);
|
||||
|
||||
|
||||
if (!isSigned && book.getItemMeta() != null) {
|
||||
return BookHelper.unsignBook((BookMeta) book.getItemMeta(), book.getAmount());
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package net.knarcraft.bookswithoutborders.utility;
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.config.StaticMessage;
|
||||
import net.knarcraft.bookswithoutborders.config.translation.Formatting;
|
||||
import net.knarcraft.bookswithoutborders.container.EncryptedBook;
|
||||
import net.knarcraft.bookswithoutborders.encryption.AESConfiguration;
|
||||
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
|
||||
import net.knarcraft.knarlib.formatting.StringFormatter;
|
||||
@@ -65,39 +66,43 @@ public final class BookToFromTextHelper {
|
||||
/**
|
||||
* Saves an encrypted book's contents to a .yml file
|
||||
*
|
||||
* @param path <p>The path of the folder to save to. Must end with a slash</p>
|
||||
* @param fileName <p>The name of the file to load to</p>
|
||||
* @param bookMetadata <p>Metadata about the book to save</p>
|
||||
* @param path <p>The path of the folder to save to</p>
|
||||
* @param fileName <p>The name of the file to load to</p>
|
||||
* @param encryptedBook <p>The encrypted book to save</p>
|
||||
* @throws IOException <p>If unable to save the book</p>
|
||||
*/
|
||||
public static void encryptedBookToYml(@NotNull String path, @NotNull String fileName, @NotNull BookMeta bookMetadata,
|
||||
@NotNull EncryptionStyle encryptionStyle, @NotNull String encryptionKey,
|
||||
@Nullable AESConfiguration aesConfiguration) throws IOException {
|
||||
FileConfiguration bookYml = getBookConfiguration(bookMetadata);
|
||||
public static void encryptedBookToYml(@NotNull String path, @NotNull String fileName,
|
||||
@NotNull EncryptedBook encryptedBook) throws IOException {
|
||||
FileConfiguration bookYml = getBookConfiguration(encryptedBook.bookMeta());
|
||||
|
||||
bookYml.set("Encryption.Style", encryptionStyle.toString());
|
||||
bookYml.set("Encryption.Key", encryptionKey);
|
||||
if (encryptionStyle == EncryptionStyle.AES) {
|
||||
if (aesConfiguration == null) {
|
||||
bookYml.set("Encryption.Style", encryptedBook.encryptionStyle().toString());
|
||||
bookYml.set("Encryption.Key", encryptedBook.encryptionKey());
|
||||
if (encryptedBook.encryptionStyle() == EncryptionStyle.AES) {
|
||||
if (encryptedBook.aesConfiguration() == null) {
|
||||
throw new IOException("Attempted to save AES encrypted book without supplying a configuration!");
|
||||
}
|
||||
bookYml.set("Encryption.AES.IV", EncryptionHelper.bytesToHex(aesConfiguration.iv()));
|
||||
bookYml.set("Encryption.AES.Salt", EncryptionHelper.bytesToHex(aesConfiguration.salt()));
|
||||
bookYml.set("Encryption.AES.IV", EncryptionHelper.bytesToHex(encryptedBook.aesConfiguration().iv()));
|
||||
bookYml.set("Encryption.AES.Salt", EncryptionHelper.bytesToHex(encryptedBook.aesConfiguration().salt()));
|
||||
}
|
||||
|
||||
List<String> encryptedPages = EncryptionHelper.encryptDecryptBookPages(bookMetadata, encryptionStyle,
|
||||
aesConfiguration, encryptionKey, true);
|
||||
if (encryptedPages == null || encryptedPages.isEmpty()) {
|
||||
throw new IOException("Book encryption failed!");
|
||||
// If data is non-empty, that means the book is already encrypted
|
||||
if (!encryptedBook.data().isEmpty()) {
|
||||
bookYml.set("Encryption.Data", encryptedBook.data());
|
||||
} else {
|
||||
List<String> encryptedPages = EncryptionHelper.encryptDecryptBookPages(encryptedBook.bookMeta(),
|
||||
encryptedBook.encryptionStyle(), encryptedBook.aesConfiguration(), encryptedBook.encryptionKey(), true);
|
||||
if (encryptedPages == null || encryptedPages.isEmpty()) {
|
||||
throw new IOException("Book encryption failed!");
|
||||
}
|
||||
bookYml.set("Encryption.Data", encryptedPages);
|
||||
}
|
||||
bookYml.set("Encryption.Data", encryptedPages);
|
||||
|
||||
// Make sure the plaintext cannot simply be seen in the file
|
||||
if (BooksWithoutBorders.getConfiguration().useRealEncryption()) {
|
||||
bookYml.set("Pages", null);
|
||||
}
|
||||
|
||||
bookYml.save(path + fileName + ".yml");
|
||||
bookYml.save(new File(path, fileName + ".yml"));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,8 +115,8 @@ public final class BookToFromTextHelper {
|
||||
* @return <p>Metadata for the loaded book</p>
|
||||
*/
|
||||
@Nullable
|
||||
public static BookMeta encryptedBookFromYml(@NotNull File file, @NotNull BookMeta bookMetadata,
|
||||
@NotNull String userKey, boolean forceDecrypt) {
|
||||
public static EncryptedBook encryptedBookFromYml(@NotNull File file, @NotNull BookMeta bookMetadata,
|
||||
@NotNull String userKey, boolean forceDecrypt) {
|
||||
BookMeta meta;
|
||||
|
||||
try {
|
||||
@@ -123,25 +128,21 @@ public final class BookToFromTextHelper {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the plaintext is stored in the file, don't bother with real decryption
|
||||
if (!meta.getPages().isEmpty()) {
|
||||
return meta;
|
||||
}
|
||||
|
||||
FileConfiguration bookYml = YamlConfiguration.loadConfiguration(file);
|
||||
|
||||
// If key is blank, it's either not real encrypted, or admin decryption is disabled for the book
|
||||
userKey = EncryptionHelper.sha256(userKey);
|
||||
String realKey = bookYml.getString("Encryption.Key", "");
|
||||
if (forceDecrypt) {
|
||||
userKey = realKey;
|
||||
}
|
||||
if (!userKey.equals(realKey)) {
|
||||
return null;
|
||||
if (!realKey.isBlank()) {
|
||||
if (forceDecrypt) {
|
||||
userKey = realKey;
|
||||
}
|
||||
if (!userKey.equals(realKey)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
List<String> data = bookYml.getStringList("Encryption.Data");
|
||||
if (data.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
EncryptionStyle encryptionStyle = EncryptionStyle.getFromString(bookYml.getString("Encryption.Style",
|
||||
EncryptionStyle.SUBSTITUTION.toString()));
|
||||
@@ -153,15 +154,28 @@ public final class BookToFromTextHelper {
|
||||
aesConfiguration = new AESConfiguration(iv, salt, userKey);
|
||||
}
|
||||
|
||||
// If the plaintext is stored in the file, don't bother with real decryption
|
||||
EncryptedBook encryptedBook = new EncryptedBook(meta, encryptionStyle, userKey, data, aesConfiguration);
|
||||
if (!meta.getPages().isEmpty()) {
|
||||
return encryptedBook;
|
||||
}
|
||||
|
||||
// Neither plaintext nor cipher text is stored
|
||||
if (data.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Perform the actual AES decryption
|
||||
meta.setPages(data);
|
||||
List<String> decryptedPages = EncryptionHelper.encryptDecryptBookPages(meta, encryptionStyle,
|
||||
aesConfiguration, userKey, false);
|
||||
List<String> decryptedPages = EncryptionHelper.encryptDecryptBookPages(meta, encryptionStyle, aesConfiguration,
|
||||
userKey, false);
|
||||
if (decryptedPages != null && !decryptedPages.isEmpty()) {
|
||||
meta.setPages(decryptedPages);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
return meta;
|
||||
|
||||
return encryptedBook;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -260,7 +274,7 @@ public final class BookToFromTextHelper {
|
||||
|
||||
//Update the metadata of the book with its new values
|
||||
bookMetadata.setAuthor(authorFromUUID(author));
|
||||
bookMetadata.setTitle(title.substring(0, 32));
|
||||
bookMetadata.setTitle(title.substring(0, Math.min(title.length(), 32)));
|
||||
bookMetadata.setPages(pages);
|
||||
|
||||
return bookMetadata;
|
||||
|
@@ -3,6 +3,8 @@ package net.knarcraft.bookswithoutborders.utility;
|
||||
import net.knarcraft.bookswithoutborders.BooksWithoutBorders;
|
||||
import net.knarcraft.bookswithoutborders.config.BwBConfig;
|
||||
import net.knarcraft.bookswithoutborders.config.StaticMessage;
|
||||
import net.knarcraft.bookswithoutborders.config.translation.Translatable;
|
||||
import net.knarcraft.bookswithoutborders.container.EncryptedBook;
|
||||
import net.knarcraft.bookswithoutborders.encryption.AES;
|
||||
import net.knarcraft.bookswithoutborders.encryption.AESConfiguration;
|
||||
import net.knarcraft.bookswithoutborders.encryption.EncryptionStyle;
|
||||
@@ -273,9 +275,11 @@ public final class EncryptionHelper {
|
||||
return null;
|
||||
} else {
|
||||
try {
|
||||
bookMetadata = BookToFromTextHelper.encryptedBookFromYml(file, bookMetadata, key, forceDecrypt);
|
||||
if (bookMetadata == null) {
|
||||
EncryptedBook book = BookToFromTextHelper.encryptedBookFromYml(file, bookMetadata, key, forceDecrypt);
|
||||
if (book == null) {
|
||||
throw new IllegalArgumentException();
|
||||
} else {
|
||||
bookMetadata = book.bookMeta();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Decryption failed!");
|
||||
@@ -489,13 +493,15 @@ public final class EncryptionHelper {
|
||||
fileName = cleanString(fileName);
|
||||
|
||||
//cancels saving if file is already encrypted
|
||||
File file = new File(path + fileName + ".yml");
|
||||
File file = new File(path, fileName + ".yml");
|
||||
if (file.isFile()) {
|
||||
return true;
|
||||
BooksWithoutBorders.getStringFormatter().displayErrorMessage(player, Translatable.ERROR_ENCRYPT_ALREADY_SAVED);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
BookToFromTextHelper.encryptedBookToYml(path, fileName, bookMetaData, encryptionStyle, key, aesConfiguration);
|
||||
BookToFromTextHelper.encryptedBookToYml(path, fileName,
|
||||
new EncryptedBook(bookMetaData, encryptionStyle, key, new ArrayList<>(), aesConfiguration));
|
||||
} catch (IOException exception) {
|
||||
BooksWithoutBorders.sendErrorMessage(player, "Encryption failed!");
|
||||
return false;
|
||||
|
@@ -232,7 +232,6 @@ commands:
|
||||
All txt files will be converted to yml.
|
||||
All books will be re-saved, fixing any incorrect file-names, a changed title-author separator and files using
|
||||
underscores "_" instead of spaces.
|
||||
Books encrypted using real encryption will have their decrypted contents show up in the file system.
|
||||
aliases:
|
||||
- bwbmigrate
|
||||
usage: /<command>
|
||||
|
@@ -90,6 +90,9 @@ en:
|
||||
ERROR_NO_ITEM: "You must be holding an item to use this command!"
|
||||
ERROR_TITLE_EMPTY: "You must specify the new title/display name to set!"
|
||||
ERROR_TITLE_LENGTH: "Book titles are capped at 32 characters!"
|
||||
ERROR_ENCRYPT_ALREADY_SAVED: |
|
||||
An encrypted version of this book is already saved.
|
||||
Please decrypt your previously encrypted copy first.
|
||||
# ---------------------------------- #
|
||||
# Custom formatting for some output #
|
||||
# ---------------------------------- #
|
||||
|
Reference in New Issue
Block a user