Pastes, manifest reading

This commit is contained in:
Vankka 2022-03-28 16:56:25 +03:00
parent b63be457fd
commit 68fa07eb15
No known key found for this signature in database
GPG Key ID: 6E50CB7A29B96AD0
13 changed files with 380 additions and 23 deletions

View File

@ -121,11 +121,6 @@ public class BukkitDiscordSRV extends ServerDiscordSRV<BukkitConfig, BukkitConne
return playerProvider; return playerProvider;
} }
@Override
public String version() {
return bootstrap.getPlugin().getDescription().getVersion();
}
@Override @Override
public PluginManager pluginManager() { public PluginManager pluginManager() {
return pluginManager; return pluginManager;

View File

@ -103,11 +103,6 @@ public class BungeeDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionConf
return playerProvider; return playerProvider;
} }
@Override
public String version() {
return bootstrap.getPlugin().getDescription().getVersion();
}
@Override @Override
public PluginManager pluginManager() { public PluginManager pluginManager() {
return pluginManager; return pluginManager;

View File

@ -67,10 +67,16 @@ import com.discordsrv.common.placeholder.context.GlobalTextHandlingContext;
import com.discordsrv.common.profile.ProfileManager; import com.discordsrv.common.profile.ProfileManager;
import com.discordsrv.common.storage.Storage; import com.discordsrv.common.storage.Storage;
import com.discordsrv.common.storage.StorageType; import com.discordsrv.common.storage.StorageType;
import com.fasterxml.jackson.databind.ObjectMapper;
import net.dv8tion.jda.api.JDA; import net.dv8tion.jda.api.JDA;
import okhttp3.*;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.OverridingMethodsMustInvokeSuper; import javax.annotation.OverridingMethodsMustInvokeSuper;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
@ -79,6 +85,9 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
/** /**
* DiscordSRV's implementation's common code. * DiscordSRV's implementation's common code.
@ -108,6 +117,25 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
private boolean hikariLoaded = false; private boolean hikariLoaded = false;
private LinkProvider linkProvider; private LinkProvider linkProvider;
// Version
private String version;
private String gitRevision;
private String gitBranch;
private final OkHttpClient httpClient = new OkHttpClient.Builder()
.addInterceptor(chain -> {
Request original = chain.request();
return chain.proceed(
original.newBuilder()
.removeHeader("User-Agent")
.addHeader("User-Agent", "DiscordSRV/" + version())
.build()
);
})
.callTimeout(1, TimeUnit.MINUTES)
.build();
private final ObjectMapper objectMapper = new ObjectMapper();
// Internal // Internal
private final ReentrantLock lifecycleLock = new ReentrantLock(); private final ReentrantLock lifecycleLock = new ReentrantLock();
@ -129,6 +157,38 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this); this.discordConnectionDetails = new DiscordConnectionDetailsImpl(this);
this.discordConnectionManager = new JDAConnectionManager(this); this.discordConnectionManager = new JDAConnectionManager(this);
this.channelConfig = new ChannelConfigHelper(this); this.channelConfig = new ChannelConfigHelper(this);
readManifest();
}
protected URL getManifest() {
return getClass().getClassLoader().getResource(JarFile.MANIFEST_NAME);
}
private void readManifest() {
try {
URL url = getManifest();
if (url == null) {
logger().error("Could not find manifest");
return;
}
try (InputStream inputStream = url.openStream()) {
Manifest manifest = new Manifest(inputStream);
Attributes attributes = manifest.getMainAttributes();
version = readAttribute(attributes, "Implementation-Version");
if (version == null) {
logger().error("Failed to get version from manifest");
}
gitRevision = readAttribute(attributes, "Git-Commit");
gitBranch = readAttribute(attributes, "Git-Branch");
}
} catch (IOException e) {
logger().error("Failed to read manifest", e);
}
}
private String readAttribute(Attributes attributes, String key) {
return attributes.getValue(key);
} }
// DiscordSRVApi // DiscordSRVApi
@ -191,6 +251,21 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
return linkProvider; return linkProvider;
} }
@Override
public @NotNull String version() {
return version;
}
@Override
public @Nullable String gitRevision() {
return gitRevision;
}
@Override
public @Nullable String gitBranch() {
return gitBranch;
}
@Override @Override
public ChannelConfigHelper channelConfig() { public ChannelConfigHelper channelConfig() {
return channelConfig; return channelConfig;
@ -278,6 +353,16 @@ public abstract class AbstractDiscordSRV<C extends MainConfig, CC extends Connec
} }
} }
@Override
public OkHttpClient httpClient() {
return httpClient;
}
@Override
public ObjectMapper json() {
return objectMapper;
}
// Lifecycle // Lifecycle
protected CompletableFuture<Void> invokeLifecycle(CheckedRunnable runnable, String message, boolean enable) { protected CompletableFuture<Void> invokeLifecycle(CheckedRunnable runnable, String message, boolean enable) {

View File

@ -41,8 +41,10 @@ import com.discordsrv.common.plugin.PluginManager;
import com.discordsrv.common.profile.ProfileManager; import com.discordsrv.common.profile.ProfileManager;
import com.discordsrv.common.scheduler.Scheduler; import com.discordsrv.common.scheduler.Scheduler;
import com.discordsrv.common.storage.Storage; import com.discordsrv.common.storage.Storage;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.benmanes.caffeine.cache.Caffeine; import com.github.benmanes.caffeine.cache.Caffeine;
import dev.vankka.dependencydownload.classpath.ClasspathAppender; import dev.vankka.dependencydownload.classpath.ClasspathAppender;
import okhttp3.OkHttpClient;
import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -59,7 +61,6 @@ public interface DiscordSRV extends DiscordSRVApi {
Path dataDirectory(); Path dataDirectory();
Scheduler scheduler(); Scheduler scheduler();
Console console(); Console console();
String version();
PluginManager pluginManager(); PluginManager pluginManager();
OnlineMode onlineMode(); OnlineMode onlineMode();
ClasspathAppender classpathAppender(); ClasspathAppender classpathAppender();
@ -92,6 +93,14 @@ public interface DiscordSRV extends DiscordSRVApi {
// Link Provider // Link Provider
LinkProvider linkProvider(); LinkProvider linkProvider();
// Version
@NotNull
String version();
@Nullable
String gitRevision();
@Nullable
String gitBranch();
// Config // Config
ConnectionConfigManager<? extends ConnectionConfig> connectionConfigManager(); ConnectionConfigManager<? extends ConnectionConfig> connectionConfigManager();
ConnectionConfig connectionConfig(); ConnectionConfig connectionConfig();
@ -124,6 +133,8 @@ public interface DiscordSRV extends DiscordSRVApi {
return (Caffeine<K, V>) Caffeine.newBuilder() return (Caffeine<K, V>) Caffeine.newBuilder()
.executor(scheduler().forkJoinPool()); .executor(scheduler().forkJoinPool());
} }
OkHttpClient httpClient();
ObjectMapper json();
// Lifecycle // Lifecycle
CompletableFuture<Void> invokeEnable(); CompletableFuture<Void> invokeEnable();

View File

@ -0,0 +1,48 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.paste;
public class Paste {
private final String id;
private final String url;
private final byte[] decryptionKey;
public Paste(String id, String url, byte[] decryptionKey) {
this.id = id;
this.url = url;
this.decryptionKey = decryptionKey;
}
public String id() {
return id;
}
public String url() {
return url;
}
public byte[] decryptionKey() {
return decryptionKey;
}
public Paste withDecryptionKey(byte[] decryptionKey) {
return new Paste(id, url, decryptionKey);
}
}

View File

@ -0,0 +1,25 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.paste;
public interface PasteService {
Paste uploadFile(byte[] fileContent) throws Throwable;
}

View File

@ -0,0 +1,69 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.paste.service;
import com.discordsrv.common.paste.Paste;
import com.discordsrv.common.paste.PasteService;
import org.apache.commons.lang3.ArrayUtils;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public class AESEncryptedPasteService implements PasteService {
private final PasteService service;
private final int keySize;
private final SecureRandom RANDOM = new SecureRandom();
public AESEncryptedPasteService(PasteService service, int keySize) {
this.service = service;
this.keySize = keySize;
}
@Override
public Paste uploadFile(byte[] fileContent) throws Throwable {
byte[] iv = new byte[16];
RANDOM.nextBytes(iv);
SecretKey secretKey = generateKey();
byte[] encrypted = encrypt(secretKey, fileContent, iv);
Paste paste = service.uploadFile(Base64.getEncoder().encode(ArrayUtils.addAll(iv, encrypted)));
return paste.withDecryptionKey(secretKey.getEncoded());
}
private SecretKey generateKey() throws NoSuchAlgorithmException {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(keySize);
return keyGenerator.generateKey();
}
private byte[] encrypt(SecretKey key, byte[] content, byte[] iv) throws GeneralSecurityException {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
return cipher.doFinal(content);
}
}

View File

@ -0,0 +1,70 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.paste.service;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.paste.Paste;
import com.discordsrv.common.paste.PasteService;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import okhttp3.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class BinPasteService implements PasteService {
private final DiscordSRV discordSRV;
private final String binUrl;
public BinPasteService(DiscordSRV discordSRV, String binUrl) {
this.discordSRV = discordSRV;
this.binUrl = binUrl;
}
@Override
public Paste uploadFile(byte[] fileContent) throws IOException {
ObjectNode json = discordSRV.json().createObjectNode();
ArrayNode files = json.putArray("files");
ObjectNode file = files.addObject();
// "File name must be divisible by 16" not that I care about the file name...
file.put("name", new String(Base64.getEncoder().encode(new byte[16])));
file.put("content", new String(fileContent, StandardCharsets.UTF_8));
Request request = new Request.Builder()
.url(binUrl + "/v1/post")
.post(RequestBody.create(MediaType.get("application/json"), json.toString()))
.build();
try (Response response = discordSRV.httpClient().newCall(request).execute()) {
ResponseBody responseBody = response.body();
if (responseBody == null) {
return null;
}
JsonNode responseNode = discordSRV.json().readTree(responseBody.string());
String binId = responseNode.get("bin").asText();
return new Paste(binId, binUrl + "/v1/" + binId + ".json", null);
}
}
}

View File

@ -0,0 +1,56 @@
/*
* This file is part of DiscordSRV, licensed under the GPLv3 License
* Copyright (c) 2016-2022 Austin "Scarsz" Shapiro, Henri "Vankka" Schubin and DiscordSRV contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.discordsrv.common.paste.service;
import com.discordsrv.common.DiscordSRV;
import com.discordsrv.common.paste.Paste;
import com.discordsrv.common.paste.PasteService;
import com.fasterxml.jackson.databind.JsonNode;
import okhttp3.*;
public class BytebinPasteService implements PasteService {
private final DiscordSRV discordSRV;
private final String bytebinUrl;
public BytebinPasteService(DiscordSRV discordSRV, String bytebinUrl) {
this.discordSRV = discordSRV;
this.bytebinUrl = bytebinUrl;
}
@Override
public Paste uploadFile(byte[] fileContent) throws Throwable {
Request request = new Request.Builder()
.url(bytebinUrl + "/post")
.header("Content-Encoding", "gzip")
.post(RequestBody.create(MediaType.get("application/octet-stream"), fileContent))
.build();
try (Response response = discordSRV.httpClient().newCall(request).execute()) {
ResponseBody responseBody = response.body();
if (responseBody == null) {
return null;
}
JsonNode responseNode = discordSRV.json().readTree(responseBody.string());
String key = responseNode.get("key").asText();
return new Paste(key, bytebinUrl + "/" + key, null);
}
}
}

View File

@ -85,7 +85,7 @@ public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionCon
} }
@Override @Override
public String version() { public @NotNull String version() {
return null; return null;
} }

View File

@ -60,7 +60,7 @@ public class MockDiscordSRV extends AbstractDiscordSRV<MainConfig, ConnectionCon
} }
@Override @Override
public String version() { public @NotNull String version() {
return null; return null;
} }

View File

@ -112,11 +112,6 @@ public class SpongeDiscordSRV extends ServerDiscordSRV<MainConfig, ConnectionCon
return playerProvider; return playerProvider;
} }
@Override
public String version() {
return pluginContainer.metadata().version().toString();
}
@Override @Override
public PluginManager pluginManager() { public PluginManager pluginManager() {
return pluginManager; return pluginManager;

View File

@ -37,7 +37,10 @@ import com.velocitypowered.api.proxy.ProxyServer;
import dev.vankka.dependencydownload.classpath.ClasspathAppender; import dev.vankka.dependencydownload.classpath.ClasspathAppender;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.jar.JarFile;
public class VelocityDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionConfig> { public class VelocityDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionConfig> {
@ -71,6 +74,16 @@ public class VelocityDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionCo
load(); load();
} }
@Override
protected URL getManifest() {
ClassLoader classLoader = getClass().getClassLoader();
if (classLoader instanceof URLClassLoader) {
return ((URLClassLoader) classLoader).findResource(JarFile.MANIFEST_NAME);
} else {
throw new IllegalStateException("Class not loaded by a URLClassLoader, unable to get manifest");
}
}
public Object plugin() { public Object plugin() {
return plugin; return plugin;
} }
@ -108,11 +121,6 @@ public class VelocityDiscordSRV extends ProxyDiscordSRV<MainConfig, ConnectionCo
return playerProvider; return playerProvider;
} }
@Override
public String version() {
return container().getDescription().getVersion().orElseThrow(() -> new IllegalStateException("No version"));
}
@Override @Override
public PluginManager pluginManager() { public PluginManager pluginManager() {
return pluginManager; return pluginManager;