Compare commits
No commits in common. "master" and "dev-threading" have entirely different histories.
master
...
dev-thread
19 changed files with 369 additions and 896 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -128,7 +128,7 @@ publish/
|
||||||
*.publishproj
|
*.publishproj
|
||||||
|
|
||||||
# NuGet Packages Directory
|
# NuGet Packages Directory
|
||||||
## TO!DO: If you have NuGet Package Restore enabled, uncomment the next line
|
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
|
||||||
#packages/
|
#packages/
|
||||||
|
|
||||||
# Windows Azure Build Output
|
# Windows Azure Build Output
|
||||||
|
@ -218,5 +218,3 @@ TheButtonAutoFlair/out/artifacts/Autoflair/Autoflair.jar
|
||||||
*.name
|
*.name
|
||||||
.idea/compiler.xml
|
.idea/compiler.xml
|
||||||
*.xml
|
*.xml
|
||||||
|
|
||||||
upload_key
|
|
||||||
|
|
|
@ -10,7 +10,6 @@ org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
|
||||||
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
|
org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
|
||||||
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
|
||||||
org.eclipse.jdt.core.compiler.compliance=1.8
|
org.eclipse.jdt.core.compiler.compliance=1.8
|
||||||
org.eclipse.jdt.core.compiler.problem.APILeak=warning
|
|
||||||
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
|
org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
|
org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
|
||||||
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
|
org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
|
||||||
|
@ -18,7 +17,7 @@ org.eclipse.jdt.core.compiler.problem.deadCode=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.deprecation=warning
|
org.eclipse.jdt.core.compiler.problem.deprecation=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
|
org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
|
||||||
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
|
org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
|
||||||
org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
|
org.eclipse.jdt.core.compiler.problem.discouragedReference=ignore
|
||||||
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
|
org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
|
||||||
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
|
org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
|
||||||
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
|
org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
|
||||||
|
@ -70,16 +69,12 @@ org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
|
||||||
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
|
org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
|
||||||
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
|
org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
|
||||||
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
|
org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
|
||||||
org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
|
|
||||||
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
|
org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
|
org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
|
||||||
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
|
org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
|
org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
|
org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
|
||||||
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
|
org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
|
||||||
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
|
|
||||||
org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
|
|
||||||
org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
|
|
||||||
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
|
org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
|
||||||
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
|
org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
|
||||||
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
|
org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
|
||||||
|
|
18
.travis.yml
18
.travis.yml
|
@ -1,18 +0,0 @@
|
||||||
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
|
|
11
deploy.sh
11
deploy.sh
|
@ -1,11 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
|
@ -3,7 +3,4 @@ main: buttondevteam.website.ButtonWebsiteModule
|
||||||
version: 4.0
|
version: 4.0
|
||||||
author: NorbiPeti
|
author: NorbiPeti
|
||||||
depend:
|
depend:
|
||||||
- ThorpeCore
|
- ButtonCore
|
||||||
commands:
|
|
||||||
login:
|
|
||||||
aliases: [web, weblogin, website]
|
|
36
pom.xml
36
pom.xml
|
@ -45,6 +45,11 @@
|
||||||
<!-- <artifactSet> <includes> <include>org.shredzone.acme4j:acme4j-client</include>
|
<!-- <artifactSet> <includes> <include>org.shredzone.acme4j:acme4j-client</include>
|
||||||
<include>org.shredzone.acme4j:acme4j-utils</include> <include>org.bouncycastle:bcprov-jdk15on</include>
|
<include>org.shredzone.acme4j:acme4j-utils</include> <include>org.bouncycastle:bcprov-jdk15on</include>
|
||||||
</includes> </artifactSet> -->
|
</includes> </artifactSet> -->
|
||||||
|
<pluginExecution>
|
||||||
|
<action>
|
||||||
|
<execute />
|
||||||
|
</action>
|
||||||
|
</pluginExecution>
|
||||||
<filters>
|
<filters>
|
||||||
<filter>
|
<filter>
|
||||||
<artifact>*:*</artifact>
|
<artifact>*:*</artifact>
|
||||||
|
@ -63,9 +68,6 @@
|
||||||
</build>
|
</build>
|
||||||
<properties>
|
<properties>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
<branch>
|
|
||||||
master
|
|
||||||
</branch>
|
|
||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<repositories>
|
<repositories>
|
||||||
|
@ -87,7 +89,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.spigotmc</groupId>
|
<groupId>org.spigotmc</groupId>
|
||||||
<artifactId>spigot-api</artifactId>
|
<artifactId>spigot-api</artifactId>
|
||||||
<version>1.12.2-R0.1-SNAPSHOT</version>
|
<version>1.9.2-R0.1-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
|
||||||
|
@ -113,7 +115,7 @@
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-io -->
|
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-io -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>commons-io</groupId>
|
<groupId>org.apache.commons</groupId>
|
||||||
<artifactId>commons-io</artifactId>
|
<artifactId>commons-io</artifactId>
|
||||||
<version>1.3.2</version>
|
<version>1.3.2</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
|
@ -121,40 +123,24 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.github.TBMCPlugins.ButtonCore</groupId>
|
<groupId>com.github.TBMCPlugins.ButtonCore</groupId>
|
||||||
<artifactId>ButtonCore</artifactId>
|
<artifactId>ButtonCore</artifactId>
|
||||||
<version>${branch}-SNAPSHOT</version>
|
<version>master-SNAPSHOT</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/org.shredzone.acme4j/acme4j-client -->
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.shredzone.acme4j</groupId>
|
<groupId>org.shredzone.acme4j</groupId>
|
||||||
<artifactId>acme4j-client</artifactId>
|
<artifactId>acme4j-client</artifactId>
|
||||||
<version>2.1</version>
|
<version>0.10</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.shredzone.acme4j</groupId>
|
<groupId>org.shredzone.acme4j</groupId>
|
||||||
<artifactId>acme4j-utils</artifactId>
|
<artifactId>acme4j-utils</artifactId>
|
||||||
<version>2.1</version>
|
<version>0.10</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
|
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.bouncycastle</groupId>
|
<groupId>org.bouncycastle</groupId>
|
||||||
<artifactId>bcprov-jdk15on</artifactId>
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
<version>1.60</version>
|
<version>1.57</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<profiles>
|
|
||||||
<profile>
|
|
||||||
<id>ci</id>
|
|
||||||
<activation>
|
|
||||||
<property>
|
|
||||||
<name>env.TRAVIS_BRANCH</name>
|
|
||||||
</property>
|
|
||||||
</activation>
|
|
||||||
<properties>
|
|
||||||
<!-- Override only if necessary -->
|
|
||||||
<branch>${env.TRAVIS_BRANCH}</branch>
|
|
||||||
</properties>
|
|
||||||
</profile>
|
|
||||||
</profiles>
|
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -13,22 +13,34 @@
|
||||||
*/ //Modified
|
*/ //Modified
|
||||||
package buttondevteam.website;
|
package buttondevteam.website;
|
||||||
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import java.io.BufferedReader;
|
||||||
import buttondevteam.website.page.AcmeChallengePage;
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.Writer;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.Security;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.shredzone.acme4j.*;
|
import org.shredzone.acme4j.*;
|
||||||
import org.shredzone.acme4j.challenge.Challenge;
|
import org.shredzone.acme4j.challenge.Challenge;
|
||||||
import org.shredzone.acme4j.challenge.Http01Challenge;
|
import org.shredzone.acme4j.challenge.Http01Challenge;
|
||||||
|
import org.shredzone.acme4j.exception.AcmeConflictException;
|
||||||
import org.shredzone.acme4j.exception.AcmeException;
|
import org.shredzone.acme4j.exception.AcmeException;
|
||||||
import org.shredzone.acme4j.util.CSRBuilder;
|
import org.shredzone.acme4j.util.CSRBuilder;
|
||||||
|
import org.shredzone.acme4j.util.CertificateUtils;
|
||||||
import org.shredzone.acme4j.util.KeyPairUtils;
|
import org.shredzone.acme4j.util.KeyPairUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.io.*;
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
import java.net.URI;
|
import buttondevteam.website.page.AcmeChallengePage;
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple client test tool.
|
* A simple client test tool.
|
||||||
|
@ -37,16 +49,16 @@ import java.util.Collection;
|
||||||
*/
|
*/
|
||||||
public class AcmeClient {
|
public class AcmeClient {
|
||||||
// File name of the User Key Pair
|
// File name of the User Key Pair
|
||||||
public static final File USER_KEY_FILE = new File("user.key");
|
private static final File USER_KEY_FILE = new File("user.key");
|
||||||
|
|
||||||
// File name of the Domain Key Pair
|
// File name of the Domain Key Pair
|
||||||
public static final File DOMAIN_KEY_FILE = new File("domain.key");
|
private static final File DOMAIN_KEY_FILE = new File("domain.key");
|
||||||
|
|
||||||
// File name of the CSR
|
// File name of the CSR
|
||||||
public static final File DOMAIN_CSR_FILE = new File("domain.csr");
|
private static final File DOMAIN_CSR_FILE = new File("domain.csr");
|
||||||
|
|
||||||
// File name of the signed certificate
|
// File name of the signed certificate
|
||||||
public static final File DOMAIN_CHAIN_FILE = new File("domain-chain.crt");
|
private static final File DOMAIN_CHAIN_FILE = new File("domain-chain.crt");
|
||||||
|
|
||||||
// RSA key size of generated key pairs
|
// RSA key size of generated key pairs
|
||||||
private static final int KEY_SIZE = 2048;
|
private static final int KEY_SIZE = 2048;
|
||||||
|
@ -67,17 +79,16 @@ public class AcmeClient {
|
||||||
|
|
||||||
// Create a session for Let's Encrypt.
|
// Create a session for Let's Encrypt.
|
||||||
// Use "acme://letsencrypt.org" for production server
|
// Use "acme://letsencrypt.org" for production server
|
||||||
Session session = new Session("acme://letsencrypt.org" + (TBMCCoreAPI.IsTestServer() ? "/staging" : ""));
|
Session session = new Session("acme://letsencrypt.org" + (TBMCCoreAPI.IsTestServer() ? "/staging" : ""),
|
||||||
|
userKeyPair);
|
||||||
|
|
||||||
// Get the Registration to the account.
|
// Get the Registration to the account.
|
||||||
// If there is no account yet, create a new one.
|
// If there is no account yet, create a new one.
|
||||||
Account acc = findOrRegisterAccount(session, userKeyPair);
|
Registration reg = findOrRegisterAccount(session);
|
||||||
|
|
||||||
Order order = acc.newOrder().domains(domains).create();
|
|
||||||
|
|
||||||
// Separately authorize every requested domain.
|
// Separately authorize every requested domain.
|
||||||
for (Authorization auth : order.getAuthorizations()) {
|
for (String domain : domains) {
|
||||||
authorize(auth);
|
authorize(reg, domain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load or create a key pair for the domains. This should not be the userKeyPair!
|
// Load or create a key pair for the domains. This should not be the userKeyPair!
|
||||||
|
@ -93,44 +104,19 @@ public class AcmeClient {
|
||||||
csrb.write(out);
|
csrb.write(out);
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.info("Ordering certificate...");
|
// Now request a signed certificate.
|
||||||
// Order the certificate
|
Certificate certificate = reg.requestCertificate(csrb.getEncoded());
|
||||||
order.execute(csrb.getEncoded());
|
|
||||||
|
|
||||||
// Wait for the order to complete
|
|
||||||
try {
|
|
||||||
int attempts = 10;
|
|
||||||
while (order.getStatus() != Status.VALID && attempts-- > 0) {
|
|
||||||
// Did the order fail?
|
|
||||||
if (order.getStatus() == Status.INVALID) {
|
|
||||||
throw new AcmeException("Order failed... Giving up.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for a few seconds
|
|
||||||
Thread.sleep(3000L);
|
|
||||||
|
|
||||||
// Then update the status
|
|
||||||
order.update();
|
|
||||||
if (order.getStatus() != Status.VALID)
|
|
||||||
LOG.info("Not yet...");
|
|
||||||
}
|
|
||||||
} catch (InterruptedException ex) {
|
|
||||||
LOG.error("interrupted", ex);
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the certificate
|
|
||||||
Certificate certificate = order.getCertificate();
|
|
||||||
|
|
||||||
if (certificate == null)
|
|
||||||
throw new AcmeException("Certificate is null. Wot.");
|
|
||||||
|
|
||||||
LOG.info("Success! The certificate for domains " + domains + " has been generated!");
|
LOG.info("Success! The certificate for domains " + domains + " has been generated!");
|
||||||
LOG.info("Certificate URL: " + certificate.getLocation());
|
LOG.info("Certificate URI: " + certificate.getLocation());
|
||||||
|
|
||||||
|
// Download the leaf certificate and certificate chain.
|
||||||
|
X509Certificate cert = certificate.download();
|
||||||
|
X509Certificate[] chain = certificate.downloadChain();
|
||||||
|
|
||||||
// Write a combined file containing the certificate and chain.
|
// Write a combined file containing the certificate and chain.
|
||||||
try (FileWriter fw = new FileWriter(DOMAIN_CHAIN_FILE)) {
|
try (FileWriter fw = new FileWriter(DOMAIN_CHAIN_FILE)) {
|
||||||
certificate.writeCertificate(fw);
|
CertificateUtils.writeX509CertificateChain(fw, cert, chain);
|
||||||
}
|
}
|
||||||
|
|
||||||
// That's all! Configure your web server to use the DOMAIN_KEY_FILE and
|
// That's all! Configure your web server to use the DOMAIN_KEY_FILE and
|
||||||
|
@ -157,34 +143,37 @@ public class AcmeClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Finds your {@link Account} at the ACME server. It will be found by your user's public key. If your key is not known to the server yet, a new registration will be created.
|
* Finds your {@link Registration} at the ACME server. It will be found by your user's public key. If your key is not known to the server yet, a new registration will be created.
|
||||||
|
* <p>
|
||||||
|
* This is a simple way of finding your {@link Registration}. A better way is to get the URI of your new registration with {@link Registration#getLocation()} and store it somewhere. If you need to
|
||||||
|
* get access to your account later, reconnect to it via {@link Registration#bind(Session, URI)} by using the stored location.
|
||||||
*
|
*
|
||||||
* @param session
|
* @param session
|
||||||
* {@link Session} to bind with
|
* {@link Session} to bind with
|
||||||
* @param kp The user keypair
|
* @return {@link Registration} connected to your account
|
||||||
* @return {@link Account} connected to your account
|
|
||||||
*/
|
*/
|
||||||
private Account findOrRegisterAccount(Session session, KeyPair kp) throws AcmeException, IOException {
|
private Registration findOrRegisterAccount(Session session) throws AcmeException, IOException {
|
||||||
Account acc;
|
Registration reg;
|
||||||
|
|
||||||
URI loc = ButtonWebsiteModule.getRegistration();
|
try {
|
||||||
if (loc != null) {
|
// Try to create a new Registration.
|
||||||
LOG.info("Loading account from file");
|
reg = new RegistrationBuilder().create(session);
|
||||||
return new Login(loc.toURL(), kp, session).getAccount();
|
LOG.info("Registered a new user, URI: " + reg.getLocation());
|
||||||
|
|
||||||
|
// This is a new account. Let the user accept the Terms of Service.
|
||||||
|
// We won't be able to authorize domains until the ToS is accepted.
|
||||||
|
URI agreement = reg.getAgreement();
|
||||||
|
LOG.info("Terms of Service: " + agreement);
|
||||||
|
acceptAgreement(reg, agreement);
|
||||||
|
|
||||||
|
} catch (AcmeConflictException ex) {
|
||||||
|
// The Key Pair is already registered. getLocation() contains the
|
||||||
|
// URL of the existing registration's location. Bind it to the session.
|
||||||
|
reg = Registration.bind(session, ex.getLocation());
|
||||||
|
LOG.info("Account does already exist, URI: " + reg.getLocation(), ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to create a new Registration.
|
return reg;
|
||||||
AccountBuilder ab = new AccountBuilder().useKeyPair(kp);
|
|
||||||
|
|
||||||
// This is a new account. Let the user accept the Terms of Service.
|
|
||||||
// We won't be able to authorize domains until the ToS is accepted.
|
|
||||||
URI agreement = session.getMetadata().getTermsOfService();
|
|
||||||
acceptAgreement(ab, agreement);
|
|
||||||
acc = ab.create(session);
|
|
||||||
LOG.info("Registered a new user, URI: " + acc.getLocation());
|
|
||||||
ButtonWebsiteModule.storeRegistration(acc.getLocation());
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -192,18 +181,18 @@ public class AcmeClient {
|
||||||
* <p>
|
* <p>
|
||||||
* You need separate authorizations for subdomains (e.g. "www" subdomain). Wildcard certificates are not currently supported.
|
* You need separate authorizations for subdomains (e.g. "www" subdomain). Wildcard certificates are not currently supported.
|
||||||
*
|
*
|
||||||
* @param auth
|
* @param reg
|
||||||
* {@link Authorization} for the domain
|
* {@link Registration} of your account
|
||||||
|
* @param domain
|
||||||
|
* Name of the domain to authorize
|
||||||
*/
|
*/
|
||||||
private void authorize(Authorization auth) throws AcmeException {
|
private void authorize(Registration reg, String domain) throws AcmeException {
|
||||||
LOG.info("Authorization for domain " + auth.getDomain());
|
// Authorize the domain.
|
||||||
|
Authorization auth = reg.authorizeDomain(domain);
|
||||||
|
LOG.info("Authorization for domain " + domain);
|
||||||
|
|
||||||
// The authorization is already valid. No need to process a challenge.
|
// Find the desired challenge and prepare it.
|
||||||
if (auth.getStatus() == Status.VALID) {
|
Challenge challenge = httpChallenge(auth, domain);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Challenge challenge = httpChallenge(auth);
|
|
||||||
|
|
||||||
if (challenge == null) {
|
if (challenge == null) {
|
||||||
throw new AcmeException("No challenge found");
|
throw new AcmeException("No challenge found");
|
||||||
|
@ -239,7 +228,7 @@ public class AcmeClient {
|
||||||
|
|
||||||
// All reattempts are used up and there is still no valid authorization?
|
// All reattempts are used up and there is still no valid authorization?
|
||||||
if (challenge.getStatus() != Status.VALID) {
|
if (challenge.getStatus() != Status.VALID) {
|
||||||
throw new AcmeException("Failed to pass the challenge for domain " + auth.getDomain() + ", ... Giving up.");
|
throw new AcmeException("Failed to pass the challenge for domain " + domain + ", ... Giving up.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -253,35 +242,44 @@ public class AcmeClient {
|
||||||
*
|
*
|
||||||
* @param auth
|
* @param auth
|
||||||
* {@link Authorization} to find the challenge in
|
* {@link Authorization} to find the challenge in
|
||||||
|
* @param domain
|
||||||
|
* Domain name to be authorized
|
||||||
* @return {@link Challenge} to verify
|
* @return {@link Challenge} to verify
|
||||||
*/
|
*/
|
||||||
public Challenge httpChallenge(Authorization auth) throws AcmeException {
|
@SuppressWarnings("unused")
|
||||||
|
public Challenge httpChallenge(Authorization auth, String domain) throws AcmeException {
|
||||||
// Find a single http-01 challenge
|
// Find a single http-01 challenge
|
||||||
Http01Challenge challenge = auth.findChallenge(Http01Challenge.TYPE);
|
Http01Challenge challenge = auth.findChallenge(Http01Challenge.TYPE);
|
||||||
if (challenge == null) {
|
if (challenge == null) {
|
||||||
throw new AcmeException("Found no " + Http01Challenge.TYPE + " challenge, don't know what to do...");
|
throw new AcmeException("Found no " + Http01Challenge.TYPE + " challenge, don't know what to do...");
|
||||||
}
|
}
|
||||||
LOG.info("Storing the challenge data.");
|
if (ButtonWebsiteModule.PORT == 80)
|
||||||
LOG.info("It should be reachable at: http://" + auth.getDomain() + "/.well-known/acme-challenge/" + challenge.getToken());
|
LOG.info("Storing the challenge data.");
|
||||||
ButtonWebsiteModule.addHttpPage(new AcmeChallengePage(challenge.getToken(), challenge.getAuthorization()));
|
else
|
||||||
ButtonWebsiteModule.startHttp();
|
LOG.info("Store the challenge data! Can't do automatically.");
|
||||||
try {
|
LOG.info("It should be reachable at: http://" + domain + "/.well-known/acme-challenge/" + challenge.getToken());
|
||||||
Thread.sleep(1000); // Just to make sure
|
LOG.info("File name: " + challenge.getToken());
|
||||||
} catch (InterruptedException e) {
|
LOG.info("Content: " + challenge.getAuthorization());
|
||||||
}
|
LOG.info("Press any key to continue...");
|
||||||
|
if (ButtonWebsiteModule.PORT != 80)
|
||||||
|
try {
|
||||||
|
System.in.read();
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
ButtonWebsiteModule.addPage(new AcmeChallengePage(challenge.getToken(), challenge.getAuthorization()));
|
||||||
return challenge;
|
return challenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Presents the user a link to the Terms of Service, and asks for confirmation. If the user denies confirmation, an exception is thrown.
|
* Presents the user a link to the Terms of Service, and asks for confirmation. If the user denies confirmation, an exception is thrown.
|
||||||
*
|
*
|
||||||
* @param ab
|
* @param reg
|
||||||
* {@link AccountBuilder} for the user
|
* {@link Registration} User's registration
|
||||||
* @param agreement
|
* @param agreement
|
||||||
* {@link URI} of the Terms of Service
|
* {@link URI} of the Terms of Service
|
||||||
*/
|
*/
|
||||||
public void acceptAgreement(AccountBuilder ab, URI agreement) throws AcmeException, IOException {
|
public void acceptAgreement(Registration reg, URI agreement) throws AcmeException, IOException {
|
||||||
LOG.info("Terms of Service: " + agreement);
|
|
||||||
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
|
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
|
||||||
System.out.println("Do you accept the terms? (y/n)");
|
System.out.println("Do you accept the terms? (y/n)");
|
||||||
if (br.readLine().equalsIgnoreCase("y\n")) {
|
if (br.readLine().equalsIgnoreCase("y\n")) {
|
||||||
|
@ -289,7 +287,7 @@ public class AcmeClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Motify the Registration and accept the agreement
|
// Motify the Registration and accept the agreement
|
||||||
ab.agreeToTermsOfService();
|
reg.modify().setAgreement(agreement).commit();
|
||||||
LOG.info("Updated user's ToS");
|
LOG.info("Updated user's ToS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
package buttondevteam.website;
|
package buttondevteam.website;
|
||||||
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import java.io.*;
|
||||||
import buttondevteam.lib.chat.TBMCChatAPI;
|
import java.net.InetAddress;
|
||||||
import buttondevteam.website.io.IOHelper;
|
import java.net.InetSocketAddress;
|
||||||
import buttondevteam.website.page.*;
|
import java.security.KeyPair;
|
||||||
import com.sun.net.httpserver.HttpServer;
|
import java.security.KeyStore;
|
||||||
import com.sun.net.httpserver.HttpsConfigurator;
|
import java.security.PrivateKey;
|
||||||
import com.sun.net.httpserver.HttpsParameters;
|
import java.security.Security;
|
||||||
import com.sun.net.httpserver.HttpsServer;
|
import java.security.cert.CertificateFactory;
|
||||||
|
import java.util.Calendar;
|
||||||
|
|
||||||
|
import javax.net.ssl.*;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.bouncycastle.openssl.PEMKeyPair;
|
import org.bouncycastle.openssl.PEMKeyPair;
|
||||||
import org.bouncycastle.openssl.PEMParser;
|
import org.bouncycastle.openssl.PEMParser;
|
||||||
|
@ -15,40 +20,26 @@ import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.plugin.java.JavaPlugin;
|
import org.bukkit.plugin.java.JavaPlugin;
|
||||||
|
|
||||||
import javax.net.ssl.*;
|
import com.sun.net.httpserver.HttpsConfigurator;
|
||||||
import java.io.*;
|
import com.sun.net.httpserver.HttpsParameters;
|
||||||
import java.net.*;
|
import com.sun.net.httpserver.HttpsServer;
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyStore;
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
import java.security.PrivateKey;
|
import buttondevteam.website.page.*;
|
||||||
import java.security.Security;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.security.cert.CertificateFactory;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.concurrent.ArrayBlockingQueue;
|
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class ButtonWebsiteModule extends JavaPlugin {
|
public class ButtonWebsiteModule extends JavaPlugin {
|
||||||
|
public static final int PORT = 8080;
|
||||||
private static HttpsServer server;
|
private static HttpsServer server;
|
||||||
/**
|
|
||||||
* For ACME validation and user redirection
|
|
||||||
*/
|
|
||||||
private static HttpServer httpserver;
|
|
||||||
private static boolean enabled;
|
|
||||||
|
|
||||||
public ButtonWebsiteModule() {
|
public ButtonWebsiteModule() {
|
||||||
try {
|
try {
|
||||||
int ps = getConfig().getInt("https-port", 443);
|
server = HttpsServer.create(new InetSocketAddress((InetAddress) null, PORT), 10);
|
||||||
int p = getConfig().getInt("http-port", 80);
|
|
||||||
server = HttpsServer.create(new InetSocketAddress((InetAddress) null, ps), 10);
|
|
||||||
httpserver = HttpServer.create(new InetSocketAddress((InetAddress) null, p), 10);
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||||
|
|
||||||
// initialise the keystore
|
// initialise the keystore
|
||||||
char[] password = "password".toCharArray();
|
char[] password = "password".toCharArray();
|
||||||
KeyStore ks = KeyStore.getInstance("JKS");
|
KeyStore ks = KeyStore.getInstance("JKS");
|
||||||
File certfile = AcmeClient.DOMAIN_CHAIN_FILE; /* your cert path */
|
String certfile = "domain-chain.crt"; /* your cert path */
|
||||||
File keystoreFile = new File("keystore.keystore");
|
File keystoreFile = new File("keystore.keystore");
|
||||||
|
|
||||||
ks.load(keystoreFile.exists() ? new FileInputStream(keystoreFile) : null, password);
|
ks.load(keystoreFile.exists() ? new FileInputStream(keystoreFile) : null, password);
|
||||||
|
@ -59,9 +50,9 @@ public class ButtonWebsiteModule extends JavaPlugin {
|
||||||
|
|
||||||
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
CertificateFactory cf = CertificateFactory.getInstance("X.509");
|
||||||
InputStream certstream = fullStream(certfile);
|
InputStream certstream = fullStream(certfile);
|
||||||
Certificate[] certs = cf.generateCertificates(certstream).toArray(new Certificate[0]);
|
Certificate[] certs = cf.generateCertificates(certstream).stream().toArray(Certificate[]::new);
|
||||||
|
|
||||||
BufferedReader br = new BufferedReader(new FileReader(AcmeClient.DOMAIN_KEY_FILE));
|
BufferedReader br = new BufferedReader(new FileReader("domain.key"));
|
||||||
|
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
|
||||||
|
@ -108,93 +99,42 @@ public class ButtonWebsiteModule extends JavaPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
enabled = true;
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
TBMCCoreAPI.SendException("An error occurred while starting the webserver!", e);
|
TBMCCoreAPI.SendException("An error occured while starting the webserver!", e);
|
||||||
enabled = false; //It's not even enabled yet, so we need a variable
|
getServer().getPluginManager().disablePlugin(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
if (!enabled) {
|
|
||||||
getServer().getPluginManager().disablePlugin(this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
addPage(new IndexPage());
|
addPage(new IndexPage());
|
||||||
addPage(new LoginPage());
|
|
||||||
addPage(new ProfilePage());
|
|
||||||
addPage(new BuildNotificationsPage());
|
|
||||||
addPage(new BridgePage());
|
|
||||||
TBMCCoreAPI.RegisterUserClass(WebUser.class);
|
|
||||||
TBMCChatAPI.AddCommand(this, LoginCommand.class);
|
|
||||||
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
|
Bukkit.getScheduler().runTaskAsynchronously(this, () -> {
|
||||||
this.getLogger().info("Starting webserver...");
|
this.getLogger().info("Starting webserver...");
|
||||||
server.setExecutor(
|
|
||||||
new ThreadPoolExecutor(4, 8, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100)));
|
|
||||||
httpserver.createContext("/", exchange -> IOHelper.SendResponse(IOHelper.Redirect("https://server.figytuna.com/", exchange)));
|
|
||||||
final Calendar calendar = Calendar.getInstance();
|
|
||||||
if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY && !TBMCCoreAPI.IsTestServer()) { // Only update every week
|
|
||||||
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
|
|
||||||
AcmeClient.main("server.figytuna.com"); // Task is running async so we don't need an extra thread
|
|
||||||
}
|
|
||||||
((Runnable) server::start).run(); // Totally normal way of calling a method
|
((Runnable) server::start).run(); // Totally normal way of calling a method
|
||||||
if (!httpstarted)
|
|
||||||
httpserver.start();
|
|
||||||
this.getLogger().info("Webserver started");
|
this.getLogger().info("Webserver started");
|
||||||
|
final Calendar calendar = Calendar.getInstance();
|
||||||
|
if (calendar.get(Calendar.DAY_OF_WEEK) == Calendar.FRIDAY) { // Only update every week
|
||||||
|
Thread t = new Thread(() -> AcmeClient.main("server.figytuna.com"));
|
||||||
|
t.setContextClassLoader(getClass().getClassLoader());
|
||||||
|
t.start();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean httpstarted = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used to start the server when the ACME client needs it
|
|
||||||
*/
|
|
||||||
static void startHttp() {
|
|
||||||
httpserver.start();
|
|
||||||
httpstarted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds a new page/endpoint to the website. This method needs to be called before the server finishes loading (onEnable).
|
* Adds a new page/endpoint to the website. This method needs to be called before the server finishes loading (onEnable).
|
||||||
*/
|
*/
|
||||||
public static void addPage(Page page) {
|
public static void addPage(Page page) {
|
||||||
if (!enabled)
|
|
||||||
return;
|
|
||||||
server.createContext("/" + page.GetName(), page);
|
server.createContext("/" + page.GetName(), page);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static InputStream fullStream(String fname) throws IOException {
|
||||||
* Adds an <b>insecure</b> endpoint to the website. This should be avoided when possible.
|
FileInputStream fis = new FileInputStream(fname);
|
||||||
*/
|
|
||||||
public static void addHttpPage(Page page) {
|
|
||||||
if (!enabled)
|
|
||||||
return;
|
|
||||||
httpserver.createContext("/" + page.GetName(), page);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void storeRegistration(URL location) {
|
|
||||||
final ButtonWebsiteModule plugin = getPlugin(ButtonWebsiteModule.class);
|
|
||||||
plugin.getConfig().set("registration", location.toString());
|
|
||||||
plugin.saveConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
static URI getRegistration() {
|
|
||||||
try {
|
|
||||||
String str = getPlugin(ButtonWebsiteModule.class).getConfig().getString("registration");
|
|
||||||
return str == null ? null : new URI(str);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static InputStream fullStream(File f) throws IOException {
|
|
||||||
FileInputStream fis = new FileInputStream(f);
|
|
||||||
DataInputStream dis = new DataInputStream(fis);
|
DataInputStream dis = new DataInputStream(fis);
|
||||||
byte[] bytes = new byte[dis.available()];
|
byte[] bytes = new byte[dis.available()];
|
||||||
dis.readFully(bytes);
|
dis.readFully(bytes);
|
||||||
dis.close();
|
dis.close();
|
||||||
return new ByteArrayInputStream(bytes);
|
ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
|
||||||
|
return bais;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
package buttondevteam.website;
|
|
||||||
|
|
||||||
import buttondevteam.lib.chat.CommandClass;
|
|
||||||
import buttondevteam.lib.chat.PlayerCommandBase;
|
|
||||||
import buttondevteam.website.page.LoginPage;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
|
|
||||||
@CommandClass
|
|
||||||
public class LoginCommand extends PlayerCommandBase {
|
|
||||||
@Override //TODO: Ask about linking already existing accounts, to prevent linking someone else's
|
|
||||||
public boolean OnCommand(Player player, String s, String[] strings) {
|
|
||||||
String state = LoginPage.generateState("minecraft", player.getUniqueId().toString()).toString();
|
|
||||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "tellraw " + player.getName() + " [\"\",{\"text\":\"Please \",\"color\":\"aqua\"},{\"text\":\"Click Here\",\"color\":\"aqua\",\"bold\":true,\"underlined\":true,\"clickEvent\":{\"action\":\"open_url\",\"value\":\"https://server.figytuna.com/login?type=minecraft&state=" + state + "\"}},{\"text\":\" to log in to our site using your Minecraft account.\",\"color\":\"aqua\",\"bold\":false,\"underlined\":false}]");
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] GetHelpText(String s) {
|
|
||||||
return new String[]{//
|
|
||||||
"§6---- Login ----", //
|
|
||||||
"This command allows you to log in to our website using your Minecraft account.", //
|
|
||||||
"If you are already logged in to the site, you can connect your MC account to it.", //
|
|
||||||
"This is good for getting Minecraft rewards if you're a patron for example." //
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
package buttondevteam.website;
|
|
||||||
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import buttondevteam.lib.player.ChromaGamerBase;
|
|
||||||
import buttondevteam.lib.player.PlayerData;
|
|
||||||
import buttondevteam.lib.player.UserClass;
|
|
||||||
|
|
||||||
@UserClass(foldername = "web")
|
|
||||||
public class WebUser extends ChromaGamerBase {
|
|
||||||
private UUID uuid;
|
|
||||||
|
|
||||||
public UUID getUUID() {
|
|
||||||
if (uuid == null)
|
|
||||||
uuid = UUID.fromString(getFileName());
|
|
||||||
return uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PlayerData<UUID> sessionID() {
|
|
||||||
return data(new UUID(0, 0)); //It's used with toString() directly, so can't be null
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,5 @@
|
||||||
package buttondevteam.website.io;
|
package buttondevteam.website.io;
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.time.Period;
|
import java.time.Period;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
@ -9,6 +7,8 @@ import java.time.ZonedDateTime;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
public class Cookies extends HashMap<String, Cookie> {
|
public class Cookies extends HashMap<String, Cookie> {
|
||||||
private static final long serialVersionUID = -328053564170765287L;
|
private static final long serialVersionUID = -328053564170765287L;
|
||||||
|
|
||||||
|
@ -30,11 +30,11 @@ public class Cookies extends HashMap<String, Cookie> {
|
||||||
this.expiretime = ZonedDateTime.now(ZoneId.of("GMT")).format(DateTimeFormatter.RFC_1123_DATE_TIME);
|
this.expiretime = ZonedDateTime.now(ZoneId.of("GMT")).format(DateTimeFormatter.RFC_1123_DATE_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddHeaders(HttpExchange exchange) {
|
public void SendHeaders(HttpExchange exchange) {
|
||||||
for (Entry<String, Cookie> item : entrySet())
|
for (Entry<String, Cookie> item : entrySet())
|
||||||
exchange.getResponseHeaders().add("Set-Cookie",
|
exchange.getResponseHeaders().add("Set-Cookie",
|
||||||
item.getKey() + "=" + item.getValue().getValue() + "; expires=" + expiretime + "; Secure; HttpOnly; Domain=figytuna.com"); //Allow for frontend
|
item.getKey() + "=" + item.getValue().getValue() + "; expires=" + expiretime);
|
||||||
exchange.getResponseHeaders().add("Set-Cookie", "expiretime=" + expiretime + "; expires=" + expiretime + "; Secure; HttpOnly; Domain=figytuna.com");
|
exchange.getResponseHeaders().add("Set-Cookie", "expiretime=" + expiretime + "; expires=" + expiretime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Cookies add(Cookie cookie) {
|
public Cookies add(Cookie cookie) {
|
||||||
|
|
|
@ -1,205 +1,148 @@
|
||||||
package buttondevteam.website.io;
|
package buttondevteam.website.io;
|
||||||
|
|
||||||
import buttondevteam.lib.player.ChromaGamerBase;
|
import java.io.BufferedOutputStream;
|
||||||
import buttondevteam.website.WebUser;
|
import java.io.ByteArrayInputStream;
|
||||||
import com.google.gson.JsonElement;
|
import java.io.IOException;
|
||||||
import com.google.gson.JsonObject;
|
import java.nio.charset.StandardCharsets;
|
||||||
import com.google.gson.JsonParser;
|
import java.util.HashMap;
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import java.util.Map;
|
||||||
import org.apache.commons.io.IOUtils;
|
import java.util.Map.Entry;
|
||||||
import org.bukkit.Bukkit;
|
import java.util.UUID;
|
||||||
|
import java.util.logging.Level;
|
||||||
import java.io.BufferedOutputStream;
|
|
||||||
import java.io.ByteArrayInputStream;
|
import org.apache.commons.io.IOUtils;
|
||||||
import java.io.IOException;
|
import org.bukkit.Bukkit;
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLDecoder;
|
import com.google.gson.JsonElement;
|
||||||
import java.nio.charset.StandardCharsets;
|
import com.google.gson.JsonObject;
|
||||||
import java.time.ZoneId;
|
import com.google.gson.JsonParser;
|
||||||
import java.time.ZonedDateTime;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
public class IOHelper {
|
||||||
import java.util.Map.Entry;
|
public static void SendResponse(Response resp) throws IOException {
|
||||||
import java.util.UUID;
|
SendResponse(resp.code, resp.content, resp.exchange);
|
||||||
|
}
|
||||||
public class IOHelper {
|
|
||||||
public static void SendResponse(Response resp) throws IOException {
|
public static void SendResponse(int code, String content, HttpExchange exchange) throws IOException {
|
||||||
if (resp == null)
|
try (BufferedOutputStream out = new BufferedOutputStream(exchange.getResponseBody())) {
|
||||||
return; // Response is already sent
|
try (ByteArrayInputStream bis = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
|
||||||
SendResponse(resp.code, resp.content, resp.exchange);
|
exchange.sendResponseHeaders(code, bis.available());
|
||||||
}
|
byte[] buffer = new byte[512];
|
||||||
|
int count;
|
||||||
public static void SendResponse(int code, String content, HttpExchange exchange) throws IOException {
|
while ((count = bis.read(buffer)) != -1) {
|
||||||
if (exchange.getRequestMethod().equalsIgnoreCase("HEAD")) {
|
out.write(buffer, 0, count);
|
||||||
exchange.sendResponseHeaders(200, -1); // -1 indicates no data
|
}
|
||||||
//exchange.getResponseBody().close(); - No stream is created for HEAD requests
|
}
|
||||||
return;
|
}
|
||||||
}
|
exchange.getResponseBody().close();
|
||||||
try (BufferedOutputStream out = new BufferedOutputStream(exchange.getResponseBody())) {
|
}
|
||||||
try (ByteArrayInputStream bis = new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) {
|
|
||||||
try {
|
public static String GetPOST(HttpExchange exchange) {
|
||||||
exchange.sendResponseHeaders(code, bis.available());
|
try {
|
||||||
} catch (IOException e) {
|
if (exchange.getRequestBody().available() == 0)
|
||||||
if (!e.getMessage().equals("headers already sent"))
|
return "";
|
||||||
throw e; // If an error occurs after sending the response headers send the error page even if the headers are for the original
|
String content = IOUtils.toString(exchange.getRequestBody(), "UTF-8");
|
||||||
} // This code will send *some page* (most likely an error page) with the original headers instead of failing to do anything
|
return content;
|
||||||
byte[] buffer = new byte[512];
|
} catch (Exception e) {
|
||||||
int count;
|
e.printStackTrace();
|
||||||
while ((count = bis.read(buffer)) != -1) {
|
return "";
|
||||||
out.write(buffer, 0, count);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
public static JsonObject GetPOSTJSON(HttpExchange exchange) {
|
||||||
exchange.getResponseBody().close();
|
try {
|
||||||
}
|
String content = GetPOST(exchange);
|
||||||
|
if (content.length() == 0)
|
||||||
public static String GetPOST(HttpExchange exchange) {
|
return null;
|
||||||
try {
|
JsonElement e = new JsonParser().parse(content);
|
||||||
if (exchange.getRequestBody().available() == 0)
|
if (e == null)
|
||||||
return "";
|
return null;
|
||||||
return IOUtils.toString(exchange.getRequestBody(), "UTF-8");
|
JsonObject obj = e.getAsJsonObject();
|
||||||
} catch (Exception e) {
|
return obj;
|
||||||
e.printStackTrace();
|
} catch (Exception e) {
|
||||||
return "";
|
e.printStackTrace();
|
||||||
}
|
return null;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public static JsonObject GetPOSTJSON(HttpExchange exchange) {
|
|
||||||
try {
|
/**
|
||||||
String content = GetPOST(exchange);
|
* Sends login headers and sets the session id on the user
|
||||||
if (content.length() == 0)
|
*
|
||||||
return null;
|
* @param exchange
|
||||||
JsonElement e = new JsonParser().parse(content);
|
* @param user
|
||||||
if (e == null)
|
*/
|
||||||
return null;
|
/*public static void LoginUser(HttpExchange exchange, User user) {
|
||||||
return e.getAsJsonObject();
|
Bukkit.getLogger().fine("Logging in user: " + user);
|
||||||
} catch (Exception e) {
|
// provider.SetValues(() ->
|
||||||
e.printStackTrace();
|
// user.setSessionid(UUID.randomUUID().toString()));
|
||||||
return null;
|
user.setSessionid(UUID.randomUUID().toString());
|
||||||
}
|
new Cookies(2).add(new Cookie("user_id", user.getId() + "")).add(new Cookie("session_id", user.getSessionid()))
|
||||||
}
|
.SendHeaders(exchange);
|
||||||
|
Bukkit.getLogger().fine("Logged in user.");
|
||||||
/**
|
}
|
||||||
* Sends login headers and sets the session id on the user
|
|
||||||
*/
|
public static void LogoutUser(HttpExchange exchange, User user) {
|
||||||
public static void LoginUser(HttpExchange exchange, WebUser user) {
|
user.setSessionid(new UUID(0, 0).toString());
|
||||||
Bukkit.getLogger().fine("Logging in user: " + user);
|
SendLogoutHeaders(exchange);
|
||||||
user.sessionID().set(UUID.randomUUID());
|
}
|
||||||
user.save();
|
|
||||||
new Cookies(2).add(new Cookie("user_id", user.getUUID() + ""))
|
private static void SendLogoutHeaders(HttpExchange exchange) {
|
||||||
.add(new Cookie("session_id", user.sessionID().get().toString())).AddHeaders(exchange);
|
String expiretime = "Sat, 19 Mar 2016 23:33:00 GMT";
|
||||||
Bukkit.getLogger().fine("Logged in user.");
|
new Cookies(expiretime).add(new Cookie("user_id", "del")).add(new Cookie("session_id", "del"))
|
||||||
}
|
.SendHeaders(exchange);
|
||||||
|
}
|
||||||
public static void LogoutUser(HttpExchange exchange, WebUser user) {
|
|
||||||
user.sessionID().set(new UUID(0, 0));
|
public static void Redirect(String url, HttpExchange exchange) throws IOException {
|
||||||
user.save();
|
exchange.getResponseHeaders().add("Location", url);
|
||||||
SendLogoutHeaders(exchange);
|
IOHelper.SendResponse(303, "<a href=\"" + url + "\">If you can see this, click here to continue</a>", exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SendLogoutHeaders(HttpExchange exchange) {
|
public static Cookies GetCookies(HttpExchange exchange) {
|
||||||
String expiretime = "Sat, 19 Mar 2016 23:33:00 GMT";
|
if (!exchange.getRequestHeaders().containsKey("Cookie"))
|
||||||
new Cookies(expiretime).add(new Cookie("user_id", "del")).add(new Cookie("session_id", "del"))
|
return new Cookies();
|
||||||
.AddHeaders(exchange);
|
Map<String, String> map = new HashMap<>();
|
||||||
}
|
for (String cheader : exchange.getRequestHeaders().get("Cookie")) {
|
||||||
|
String[] spl = cheader.split("\\;\\s*");
|
||||||
public static Response Redirect(String url, HttpExchange exchange) {
|
for (String s : spl) {
|
||||||
exchange.getResponseHeaders().add("Location", url);
|
String[] kv = s.split("\\=");
|
||||||
return new Response(303, "<a href=\"" + url + "\">If you can see this, click here to continue</a>", exchange);
|
if (kv.length < 2)
|
||||||
}
|
continue;
|
||||||
|
map.put(kv[0], kv[1]);
|
||||||
public static Cookies GetCookies(HttpExchange exchange) {
|
}
|
||||||
if (!exchange.getRequestHeaders().containsKey("Cookie"))
|
}
|
||||||
return new Cookies();
|
if (!map.containsKey("expiretime"))
|
||||||
Map<String, String> map = new HashMap<>();
|
return new Cookies();
|
||||||
for (String cheader : exchange.getRequestHeaders().get("Cookie")) {
|
Cookies cookies = null;
|
||||||
String[] spl = cheader.split(";\\s*");
|
try {
|
||||||
for (String s : spl) {
|
cookies = new Cookies(map.get("expiretime"));
|
||||||
String[] kv = s.split("=");
|
for (Entry<String, String> item : map.entrySet())
|
||||||
if (kv.length < 2)
|
if (!item.getKey().equalsIgnoreCase("expiretime"))
|
||||||
continue;
|
cookies.put(item.getKey(), new Cookie(item.getKey(), item.getValue()));
|
||||||
map.put(kv[0], kv[1]);
|
} catch (Exception e) {
|
||||||
}
|
return new Cookies();
|
||||||
}
|
}
|
||||||
if (!map.containsKey("expiretime"))
|
return cookies;
|
||||||
return new Cookies();
|
}*/
|
||||||
Cookies cookies;
|
|
||||||
try {
|
/**
|
||||||
cookies = new Cookies(map.get("expiretime"));
|
* Get logged in user. It may also send logout headers if the cookies are invalid, or login headers to keep the user logged in.
|
||||||
for (Entry<String, String> item : map.entrySet())
|
*
|
||||||
if (!item.getKey().equalsIgnoreCase("expiretime"))
|
* @param exchange
|
||||||
cookies.put(item.getKey(), new Cookie(item.getKey(), item.getValue()));
|
* @return The logged in user or null if not logged in.
|
||||||
} catch (Exception e) {
|
* @throws IOException
|
||||||
return new Cookies();
|
*/
|
||||||
}
|
/*public static User GetLoggedInUser(HttpExchange exchange) throws IOException {
|
||||||
return cookies;
|
Cookies cookies = GetCookies(exchange);
|
||||||
}
|
if (!cookies.containsKey("user_id") || !cookies.containsKey("session_id"))
|
||||||
|
return null;
|
||||||
/**
|
User user = DataManager.load(User.class, Long.parseLong(cookies.get("user_id").getValue()), false);
|
||||||
* Get logged in user. It may also send logout headers if the cookies are invalid, or login headers to keep the user logged in. <b>Make sure to save the user data.</b>
|
if (user != null && cookies.get("session_id") != null
|
||||||
*
|
&& cookies.get("session_id").getValue().equals(user.getSessionid())) {
|
||||||
* @param exchange The exchange
|
if (cookies.getExpireTimeParsed().minusYears(1).isBefore(ZonedDateTime.now(ZoneId.of("GMT"))))
|
||||||
* @return The logged in user or null if not logged in.
|
LoginUser(exchange, user);
|
||||||
*/
|
return user;
|
||||||
public static WebUser GetLoggedInUser(HttpExchange exchange) {
|
} else
|
||||||
Cookies cookies = GetCookies(exchange);
|
SendLogoutHeaders(exchange);
|
||||||
if (!cookies.containsKey("user_id") || !cookies.containsKey("session_id"))
|
return null;
|
||||||
return null;
|
}*/
|
||||||
WebUser user = ChromaGamerBase.getUser(cookies.get("user_id").getValue(), WebUser.class);
|
}
|
||||||
if (user != null && cookies.get("session_id") != null
|
|
||||||
&& cookies.get("session_id").getValue().equals(user.sessionID().get().toString())) {
|
|
||||||
if (cookies.getExpireTimeParsed().minusYears(1).isBefore(ZonedDateTime.now(ZoneId.of("GMT"))))
|
|
||||||
LoginUser(exchange, user);
|
|
||||||
return user;
|
|
||||||
} else
|
|
||||||
SendLogoutHeaders(exchange);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Map<String, String> parseQueryString(HttpExchange exchange) {
|
|
||||||
String qs = exchange.getRequestURI().getRawQuery();
|
|
||||||
Map<String, String> result = new HashMap<>();
|
|
||||||
if (qs == null)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
int last = 0, next, l = qs.length();
|
|
||||||
while (last < l) {
|
|
||||||
next = qs.indexOf('&', last);
|
|
||||||
if (next == -1)
|
|
||||||
next = l;
|
|
||||||
|
|
||||||
if (next > last) {
|
|
||||||
int eqPos = qs.indexOf('=', last);
|
|
||||||
try {
|
|
||||||
if (eqPos < 0 || eqPos > next)
|
|
||||||
result.put(URLDecoder.decode(qs.substring(last, next), "utf-8"), "");
|
|
||||||
else
|
|
||||||
result.put(URLDecoder.decode(qs.substring(last, eqPos), "utf-8"),
|
|
||||||
URLDecoder.decode(qs.substring(eqPos + 1, next), "utf-8"));
|
|
||||||
} catch (UnsupportedEncodingException e) {
|
|
||||||
throw new RuntimeException(e); // will never happen, utf-8 support is mandatory for java
|
|
||||||
}
|
|
||||||
}
|
|
||||||
last = next + 1;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static HashMap<String, String> GetPOSTKeyValues(HttpExchange exchange) {
|
|
||||||
try {
|
|
||||||
String[] content = GetPOST(exchange).split("&");
|
|
||||||
HashMap<String, String> 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<>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,9 +3,9 @@ package buttondevteam.website.io;
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.HttpExchange;
|
||||||
|
|
||||||
public class Response {
|
public class Response {
|
||||||
public final int code;
|
public int code;
|
||||||
public final String content;
|
public String content;
|
||||||
public final HttpExchange exchange;
|
public HttpExchange exchange;
|
||||||
|
|
||||||
public Response(int code, String content, HttpExchange exchange) {
|
public Response(int code, String content, HttpExchange exchange) {
|
||||||
this.code = code;
|
this.code = code;
|
||||||
|
|
|
@ -6,9 +6,9 @@ import buttondevteam.website.io.Response;
|
||||||
|
|
||||||
public class AcmeChallengePage extends Page {
|
public class AcmeChallengePage extends Page {
|
||||||
|
|
||||||
public AcmeChallengePage(String token, String content) { // The page name needs to be known before server start
|
public AcmeChallengePage(String token, String content) {
|
||||||
AcmeChallengePage.token = token;
|
this.token = token;
|
||||||
AcmeChallengePage.content = content;
|
this.content = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -23,7 +23,7 @@ public class AcmeChallengePage extends Page {
|
||||||
return new Response(200, content, exchange);
|
return new Response(200, content, exchange);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String token;
|
private String token;
|
||||||
private static String content;
|
private String content;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,128 +0,0 @@
|
||||||
package buttondevteam.website.page;
|
|
||||||
|
|
||||||
import buttondevteam.website.io.Response;
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.Socket;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class BridgePage extends Page {
|
|
||||||
private final Map<String, Socket> connections = new HashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String GetName() {
|
|
||||||
return "bridge";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response handlePage(HttpExchange exchange) {
|
|
||||||
String method = exchange.getRequestMethod().toUpperCase();
|
|
||||||
String id = getConnID(exchange);
|
|
||||||
if (id == null)
|
|
||||||
return new Response(400, "No ID", exchange);
|
|
||||||
try {
|
|
||||||
Socket s;
|
|
||||||
switch (method) {
|
|
||||||
case "POST":
|
|
||||||
if (connections.containsKey(id))
|
|
||||||
connections.get(id).close();
|
|
||||||
Socket socket = new Socket("localhost", Bukkit.getPort());
|
|
||||||
socket.setKeepAlive(true);
|
|
||||||
socket.setTcpNoDelay(true);
|
|
||||||
connections.put(id, socket);
|
|
||||||
System.out.println("[BWM] Created a bridge: " + id);
|
|
||||||
return new Response(201, "You know what you created. A bridge.", exchange);
|
|
||||||
case "PUT":
|
|
||||||
s = getSocket(exchange);
|
|
||||||
if (s == null)
|
|
||||||
return new Response(400, "No connection", exchange);
|
|
||||||
if (s.isClosed())
|
|
||||||
return new Response(410, "Socket Gone", exchange);
|
|
||||||
copyStream(exchange.getRequestBody(), s.getOutputStream());
|
|
||||||
// Don't close the socket, PUT messages are sent individually
|
|
||||||
return new Response(200, "OK", exchange);
|
|
||||||
case "GET":
|
|
||||||
s = getSocket(exchange);
|
|
||||||
if (s == null)
|
|
||||||
return new Response(400, "No connection", exchange);
|
|
||||||
if (s.isClosed())
|
|
||||||
return new Response(410, "Socket Gone", exchange);
|
|
||||||
try {
|
|
||||||
exchange.sendResponseHeaders(200, 0); // Chunked transfer, any amount of data
|
|
||||||
copyStream(s.getInputStream(), exchange.getResponseBody());
|
|
||||||
exchange.getResponseBody().close(); // It'll only get here when the communication is already done
|
|
||||||
} catch (IOException ex) { //Failed to send it over HTTP, GET connection closed
|
|
||||||
closeSocket(exchange); //We only have one GET, connection over
|
|
||||||
System.out.println("[BWM] [" + id + "] over (GET): " + ex.toString());
|
|
||||||
}
|
|
||||||
return null; // Response already sent
|
|
||||||
case "DELETE":
|
|
||||||
System.out.println("[BWM] [" + id + "] delet this");
|
|
||||||
closeSocket(exchange);
|
|
||||||
return new Response(200, "OK", exchange);
|
|
||||||
default:
|
|
||||||
return new Response(403, "Unknown request", exchange);
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
if (e instanceof SocketException) {
|
|
||||||
closeSocket(exchange);
|
|
||||||
System.out.println("[BWM] [" + id + "] closed: " + e.toString());
|
|
||||||
return new Response(410, "Socket Gone because of error: " + e, exchange);
|
|
||||||
}
|
|
||||||
e.printStackTrace();
|
|
||||||
return new Response(500, "Internal Server Error: " + e, exchange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Socket getSocket(HttpExchange exchange) {
|
|
||||||
String id = getConnID(exchange);
|
|
||||||
if (id == null)
|
|
||||||
return null;
|
|
||||||
return connections.get(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getConnID(HttpExchange exchange) {
|
|
||||||
String path = exchange.getRequestURI().getPath();
|
|
||||||
if (path == null)
|
|
||||||
return null;
|
|
||||||
String[] spl = path.split("/");
|
|
||||||
if (spl.length < 2)
|
|
||||||
return null;
|
|
||||||
return spl[spl.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
private void closeSocket(HttpExchange exchange) {
|
|
||||||
Socket socket = getSocket(exchange);
|
|
||||||
if (socket == null)
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
socket.close();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
|
||||||
connections.values().remove(socket);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void copyStream(InputStream is, OutputStream os) throws IOException { // Based on IOUtils.copy()
|
|
||||||
byte[] buffer = new byte[4096];
|
|
||||||
int n;
|
|
||||||
try {
|
|
||||||
while (-1 != (n = is.read(buffer))) { // Read is blocking
|
|
||||||
os.write(buffer, 0, n);
|
|
||||||
os.flush();
|
|
||||||
}
|
|
||||||
} catch (SocketException e) { // Conection closed
|
|
||||||
os.flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean exactPage() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,100 +0,0 @@
|
||||||
package buttondevteam.website.page;
|
|
||||||
|
|
||||||
import buttondevteam.core.component.updater.PluginUpdater;
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
|
||||||
import buttondevteam.website.io.IOHelper;
|
|
||||||
import buttondevteam.website.io.Response;
|
|
||||||
import com.google.gson.Gson;
|
|
||||||
import com.google.gson.JsonElement;
|
|
||||||
import com.google.gson.JsonObject;
|
|
||||||
import com.google.gson.JsonSyntaxException;
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
|
||||||
import org.bukkit.Bukkit;
|
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
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<String>) () -> {
|
|
||||||
try {
|
|
||||||
JsonElement pubkey = fromString(TBMCCoreAPI.DownloadString("https://api.travis-ci.org/config"),
|
|
||||||
"config.notifications.webhook.public_key");
|
|
||||||
if (pubkey == null)
|
|
||||||
return null;
|
|
||||||
return pubkey.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<String, String> post = IOHelper.GetPOSTKeyValues(exchange);
|
|
||||||
try {
|
|
||||||
final List<String> 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)))) {
|
|
||||||
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) throws Exception {
|
|
||||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
|
||||||
sig.initVerify(getPublic(BuildNotificationsPage.publickey));
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,68 +0,0 @@
|
||||||
package buttondevteam.website.page;
|
|
||||||
|
|
||||||
import buttondevteam.lib.player.TBMCPlayer;
|
|
||||||
import buttondevteam.website.WebUser;
|
|
||||||
import buttondevteam.website.io.IOHelper;
|
|
||||||
import buttondevteam.website.io.Response;
|
|
||||||
import com.google.common.collect.HashBiMap;
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
public class LoginPage extends Page {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String GetName() {
|
|
||||||
return "login";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response handlePage(HttpExchange exchange) {
|
|
||||||
Map<String, String> q = IOHelper.parseQueryString(exchange);
|
|
||||||
if (q == null || !q.containsKey("type"))
|
|
||||||
return new Response(400, "400 Bad request", exchange);
|
|
||||||
String type = q.get("type");
|
|
||||||
/*if (type.equalsIgnoreCase("getstate"))
|
|
||||||
return new Response(200, "TODO", exchange); // TO!DO: Store and return a random state and check on other types
|
|
||||||
String state = q.get("state"), code = q.get("code");*/
|
|
||||||
Response nope = new Response(401, "401 Nope", exchange);
|
|
||||||
if (type.equalsIgnoreCase("minecraft")) {
|
|
||||||
//In case of Minecraft, we don't need the full OAuth2 flow, we only need to ensure the state matches
|
|
||||||
if (q.containsKey("state")) {
|
|
||||||
UUID state = UUID.fromString(q.get("state"));
|
|
||||||
if (!states.containsKey(state))
|
|
||||||
return nope;
|
|
||||||
String[] folder_id = states.get(state).split(" ");
|
|
||||||
if (!folder_id[0].equalsIgnoreCase(type)) //TODO: Use for other OAuth stuff as well
|
|
||||||
return nope;
|
|
||||||
TBMCPlayer cp = TBMCPlayer.getPlayer(UUID.fromString(folder_id[1]), TBMCPlayer.class);
|
|
||||||
WebUser wu = cp.getAs(WebUser.class);
|
|
||||||
if (wu == null) //getAs return Optional?
|
|
||||||
cp.connectWith(wu = WebUser.getUser(UUID.randomUUID().toString(), WebUser.class)); //Create new user with random UUID
|
|
||||||
IOHelper.LoginUser(exchange, wu);
|
|
||||||
states.remove(state);
|
|
||||||
return IOHelper.Redirect("https://chromagaming.figytuna.com/", exchange);
|
|
||||||
} else return new Response(418, "Now what", exchange); //Minecraft doesn't have full OAuth
|
|
||||||
}
|
|
||||||
return new Response(400, "Wut", exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value: Folder ID (don't use dashes as a separator... UUIDs contain them)
|
|
||||||
*/
|
|
||||||
private static final HashBiMap<UUID, String> states = HashBiMap.create();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a temporary state data that can be used to authenticate a user.
|
|
||||||
*
|
|
||||||
* @param type The service type. Only used to separate in temporary storage.
|
|
||||||
* @param id The user id in the service. Only used to separate in temporary storage.
|
|
||||||
* @return A unique state that can be used to authenticate a user.
|
|
||||||
*/
|
|
||||||
public static UUID generateState(String type, String id) {
|
|
||||||
UUID state = UUID.randomUUID();
|
|
||||||
states.forcePut(state, type + " " + id); //Replace existing for an user
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,55 +1,64 @@
|
||||||
package buttondevteam.website.page;
|
package buttondevteam.website.page;
|
||||||
|
|
||||||
import buttondevteam.lib.TBMCCoreAPI;
|
import java.io.PrintStream;
|
||||||
import buttondevteam.website.io.IOHelper;
|
import org.apache.commons.io.output.ByteArrayOutputStream;
|
||||||
import buttondevteam.website.io.Response;
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
import com.sun.net.httpserver.*;
|
||||||
import com.sun.net.httpserver.HttpHandler;
|
|
||||||
import org.apache.commons.io.output.ByteArrayOutputStream;
|
import buttondevteam.lib.TBMCCoreAPI;
|
||||||
|
import buttondevteam.website.io.IOHelper;
|
||||||
import java.io.PrintStream;
|
import buttondevteam.website.io.Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add using {@link buttondevteam.website.ButtonWebsiteModule#addPage(Page)}
|
* Add to {@link Main}.Pages
|
||||||
*/
|
*/
|
||||||
public abstract class Page implements HttpHandler {
|
public abstract class Page implements HttpHandler {
|
||||||
public abstract String GetName();
|
public abstract String GetName();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void handle(HttpExchange exchange) {
|
public void handle(HttpExchange exchange) {
|
||||||
try {
|
|
||||||
exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "https://chromagaming.figytuna.com");
|
//Creates a new thread to handle the request
|
||||||
if (!exactPage() || exchange.getRequestURI().getPath().equals("/" + GetName()))
|
Handler handler = new Handler(exchange);
|
||||||
IOHelper.SendResponse(handlePage(exchange));
|
handler.start();
|
||||||
else {
|
}
|
||||||
IOHelper.SendResponse(404, "404 Not found: " + exchange.getRequestURI().getPath(), exchange);
|
|
||||||
}
|
class Handler extends Thread{
|
||||||
} catch (Exception e) {
|
|
||||||
TBMCCoreAPI.SendException("Internal Server Error in ButtonWebsiteModule!", e);
|
HttpExchange exchange;
|
||||||
try {
|
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
public Handler(HttpExchange exchange){
|
||||||
PrintStream str = new PrintStream(baos);
|
this.exchange = exchange;
|
||||||
str.print("<h1>500 Internal Server Error</h1><pre>");
|
}
|
||||||
e.printStackTrace(str);
|
|
||||||
str.print("</pre>");
|
@Override
|
||||||
IOHelper.SendResponse(500, baos.toString("UTF-8"), exchange);
|
public void run(){
|
||||||
} catch (Exception e1) {
|
try {
|
||||||
TBMCCoreAPI.SendException("Exception while sending Internal Server Error in ButtonWebsiteModule!", e1);
|
exchange.getResponseHeaders().add("Access-Control-Allow-Origin", "https://tbmcplugins.github.io");
|
||||||
}
|
if (exchange.getRequestURI().getPath().equals("/" + GetName()))
|
||||||
}
|
IOHelper.SendResponse(handlePage(exchange));
|
||||||
}
|
else {
|
||||||
|
IOHelper.SendResponse(404, "404 Not found", exchange);
|
||||||
/**
|
}
|
||||||
* The main logic of the endpoint. Use IOHelper to retrieve the message sent and other things.
|
} catch (Exception e) {
|
||||||
*/
|
TBMCCoreAPI.SendException("Internal Server Error in ButtonWebsiteModule!", e);
|
||||||
public abstract Response handlePage(HttpExchange exchange);
|
try {
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
/**
|
PrintStream str = new PrintStream(baos);
|
||||||
* Whether to return 404 when the URL doesn't match the exact path
|
str.print("<h1>500 Internal Server Error</h1><pre>");
|
||||||
*
|
e.printStackTrace(str);
|
||||||
* @return Whether it should only match the page path
|
str.print("</pre>");
|
||||||
*/
|
IOHelper.SendResponse(500, baos.toString("UTF-8"), exchange);
|
||||||
public boolean exactPage() {
|
} catch (Exception e1) {
|
||||||
return true;
|
e1.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main logic of the endpoint. Use IOHelper to retrieve the message sent and other things.
|
||||||
|
*/
|
||||||
|
public abstract Response handlePage(HttpExchange exchange);
|
||||||
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
package buttondevteam.website.page;
|
|
||||||
|
|
||||||
import com.sun.net.httpserver.HttpExchange;
|
|
||||||
|
|
||||||
import buttondevteam.website.io.Response;
|
|
||||||
|
|
||||||
public class ProfilePage extends Page {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String GetName() {
|
|
||||||
return "profile";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Response handlePage(HttpExchange exchange) {
|
|
||||||
return new Response(200, "Under construction", exchange);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in a new issue