diff --git a/.gitignore b/.gitignore index 7d0ab85..d6ecfc9 100644 --- a/.gitignore +++ b/.gitignore @@ -218,3 +218,5 @@ TheButtonAutoFlair/out/artifacts/Autoflair/Autoflair.jar *.name .idea/compiler.xml *.xml + +upload_key diff --git a/.travis.yml b/.travis.yml index dff5f3a..c822f5d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1 +1,18 @@ language: java +jdk: + - oraclejdk8 +deploy: + # deploy develop to the staging environment + - provider: script + script: chmod +x deploy.sh && sh deploy.sh staging + on: + branch: dev + skip_cleanup: true + # deploy master to production + - provider: script + script: chmod +x deploy.sh && sh deploy.sh production + on: + branch: master + skip_cleanup: true +notifications: + webhooks: https://server.figytuna.com:8080/build_notifications diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..636c48d --- /dev/null +++ b/deploy.sh @@ -0,0 +1,11 @@ +#!/bin/sh +FILENAME=$(find target/ ! -name '*original*' -name '*.jar') +echo Found file: $FILENAME + +if [ $1 = 'production' ]; then +echo Production mode +echo $UPLOAD_KEY > upload_key +chmod 400 upload_key +yes | scp -B -i upload_key -o StrictHostKeyChecking=no $FILENAME travis@server.figytuna.com:/minecraft/main/plugins +fi + diff --git a/src/buttondevteam/website/ButtonWebsiteModule.java b/src/buttondevteam/website/ButtonWebsiteModule.java index a219aab..3db487d 100644 --- a/src/buttondevteam/website/ButtonWebsiteModule.java +++ b/src/buttondevteam/website/ButtonWebsiteModule.java @@ -111,6 +111,9 @@ public class ButtonWebsiteModule extends JavaPlugin { @Override public void onEnable() { addPage(new IndexPage()); + addPage(new LoginPage()); + addPage(new ProfilePage()); + addPage(new BuildNotificationsPage()); TBMCCoreAPI.RegisterUserClass(WebUser.class); Bukkit.getScheduler().runTaskAsynchronously(this, () -> { this.getLogger().info("Starting webserver..."); diff --git a/src/buttondevteam/website/WebUser.java b/src/buttondevteam/website/WebUser.java index 859e5ab..93feaee 100644 --- a/src/buttondevteam/website/WebUser.java +++ b/src/buttondevteam/website/WebUser.java @@ -17,6 +17,6 @@ public class WebUser extends ChromaGamerBase { } public PlayerData sessionID() { - return data(); + return data(null); } } diff --git a/src/buttondevteam/website/io/IOHelper.java b/src/buttondevteam/website/io/IOHelper.java index faae178..15a7432 100644 --- a/src/buttondevteam/website/io/IOHelper.java +++ b/src/buttondevteam/website/io/IOHelper.java @@ -76,6 +76,7 @@ public class IOHelper { public static void LoginUser(HttpExchange exchange, WebUser user) { Bukkit.getLogger().fine("Logging in user: " + user); user.sessionID().set(UUID.randomUUID()); + user.save(); new Cookies(2).add(new Cookie("user_id", user.getUUID() + "")) .add(new Cookie("session_id", user.sessionID().get().toString())).SendHeaders(exchange); Bukkit.getLogger().fine("Logged in user."); @@ -83,6 +84,7 @@ public class IOHelper { public static void LogoutUser(HttpExchange exchange, WebUser user) { user.sessionID().set(new UUID(0, 0)); + user.save(); SendLogoutHeaders(exchange); } @@ -125,7 +127,7 @@ public class IOHelper { } /** - * Get logged in user. It may also send logout headers if the cookies are invalid, or login headers to keep the user logged in. + * Get logged in user. It may also send logout headers if the cookies are invalid, or login headers to keep the user logged in. Make sure to save the user data. * * @param exchange * @return The logged in user or null if not logged in. @@ -174,4 +176,22 @@ public class IOHelper { } return result; } + + public static HashMap GetPOSTKeyValues(HttpExchange exchange) { + try { + String[] content = GetPOST(exchange).split("\\&"); + HashMap vars = new HashMap<>(); + for (String var : content) { + String[] spl = var.split("\\="); + if (spl.length == 1) + vars.put(spl[0], ""); + else + vars.put(spl[0], URLDecoder.decode(spl[1], "utf-8")); + } + return vars; + } catch (Exception e) { + e.printStackTrace(); + return new HashMap<>(); + } + } } diff --git a/src/buttondevteam/website/page/BuildNotificationsPage.java b/src/buttondevteam/website/page/BuildNotificationsPage.java new file mode 100644 index 0000000..fff65a1 --- /dev/null +++ b/src/buttondevteam/website/page/BuildNotificationsPage.java @@ -0,0 +1,96 @@ +package buttondevteam.website.page; + +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.Signature; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.HashMap; +import java.util.List; +import java.util.function.Supplier; + +import org.bukkit.Bukkit; + +import com.google.gson.*; +import com.sun.net.httpserver.HttpExchange; + +import buttondevteam.lib.PluginUpdater; +import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.website.io.IOHelper; +import buttondevteam.website.io.Response; + +public class BuildNotificationsPage extends Page { + + @Override + public String GetName() { + return "build_notifications"; + } + + private static final Gson gson = new Gson(); + + private static final String publickey = ((Supplier) () -> { + try { + return fromString(TBMCCoreAPI.DownloadString("https://api.travis-ci.org/config"), + "config.notifications.webhook.public_key").getAsString().replace("-----BEGIN PUBLIC KEY-----", "") + .replaceAll("\n", "").replace("-----END PUBLIC KEY-----", ""); + } catch (Exception e) { + throw new RuntimeException(e); + } + }).get(); + + @Override + public Response handlePage(HttpExchange exchange) { + HashMap post = IOHelper.GetPOSTKeyValues(exchange); + try { + final List signatures = exchange.getRequestHeaders().get("Signature"); + final String payload = post.get("payload"); + if (signatures != null && signatures.size() > 0 && post.containsKey("payload") + && verifySignature(payload.getBytes(StandardCharsets.UTF_8), + Base64.getDecoder().decode(signatures.get(0)), publickey)) { + Bukkit.getPluginManager() + .callEvent(new PluginUpdater.UpdatedEvent(gson.fromJson(payload, JsonObject.class))); + return new Response(200, "All right", exchange); + } + } catch (Exception e) { + return new Response(400, + "Invalid data, error: " + e + " If you're messing with this, stop messing with this.", exchange); // Blame the user + } + return new Response(400, "Verification failed", exchange); + } + + // Method for signature verification that initializes with the Public Key, + // updates the data to be verified and then verifies them using the signature + private boolean verifySignature(byte[] data, byte[] signature, String keystr) throws Exception { + Signature sig = Signature.getInstance("SHA1withRSA"); + sig.initVerify(getPublic(keystr)); + sig.update(data); + + return sig.verify(signature); + } + + // Method to retrieve the Public Key from a file + public PublicKey getPublic(String keystr) throws Exception { + byte[] keyBytes = Base64.getDecoder().decode(keystr); + X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes); + KeyFactory kf = KeyFactory.getInstance("RSA"); + return kf.generatePublic(spec); + } + + public static JsonElement fromString(String json, String path) throws JsonSyntaxException { + JsonObject obj = gson.fromJson(json, JsonObject.class); + String[] seg = path.split("\\."); + for (String element : seg) { + if (obj != null) { + JsonElement ele = obj.get(element); + if (!ele.isJsonObject()) + return ele; + else + obj = ele.getAsJsonObject(); + } else { + return null; + } + } + return obj; + } +} diff --git a/src/buttondevteam/website/page/Page.java b/src/buttondevteam/website/page/Page.java index 4128e8f..0df2d80 100644 --- a/src/buttondevteam/website/page/Page.java +++ b/src/buttondevteam/website/page/Page.java @@ -22,7 +22,7 @@ public abstract class Page implements HttpHandler { if (exchange.getRequestURI().getPath().equals("/" + GetName())) IOHelper.SendResponse(handlePage(exchange)); else { - IOHelper.SendResponse(404, "404 Not found", exchange); + IOHelper.SendResponse(404, "404 Not found: " + exchange.getRequestURI().getPath(), exchange); } } catch (Exception e) { TBMCCoreAPI.SendException("Internal Server Error in ButtonWebsiteModule!", e);