Compare commits

...

38 Commits

Author SHA1 Message Date
Taah 2e40ddc6bc begin oauth2 setup 2024-03-05 19:41:08 -08:00
Telesphoreo ff9cf12acc Add schematic validation to the HTTPD 2024-01-25 15:59:27 -06:00
Telesphoreo 94528860f2
Update CommandsEndpoint.java 2023-12-13 21:28:54 -06:00
Telesphoreo c83f8f1d8e
Merge pull request #12 from plexusorg/commands-route
Add commands route
2023-12-13 21:25:41 -06:00
Focusvity 31cd561b92
Add commands route 2023-12-10 15:35:30 +11:00
Telesphoreo f0582e03e6 Update dependencies 2023-11-26 00:03:16 -06:00
Telesphoreo 4e593e1b5e Update dependencies 2023-10-29 22:59:33 -05:00
Telesphoreo 026b29a98c make module name consistent 2023-09-05 18:14:43 -05:00
Telesphoreo 266a8d6384 Remove admin link 2023-09-05 17:49:50 -05:00
Telesphoreo 21ee54cf98
Update for latest version of Plex 2023-08-28 19:02:46 -05:00
Telesphoreo 492c6bd8eb
Release 1.3 2023-07-22 22:51:42 -05:00
Telesphoreo a7c76e6089 Update Gradle and dependencies 2023-04-29 15:10:24 -05:00
Telesphoreo 7a996004f0 HTTPD now supports dark mode 2023-04-03 15:39:22 -05:00
Telesphoreo 46ff09bf58 Update compatibility with Plex and Gradle 2023-03-08 13:21:31 -06:00
Telesphoreo 4426c4b4b7 Update dependencies and Gradle 2023-03-02 23:09:54 -06:00
Telesphoreo fc8811d453
Update Gradle 2022-11-27 22:18:55 -06:00
Telesphoreo 459f62e0d3
Update dependencies 2022-11-27 22:15:00 -06:00
Telesphoreo 4918cf55f3 Update Gradle + remove API 2022-08-02 20:24:27 -05:00
Telesphoreo e9395e193a
B R U H 2022-08-02 00:09:46 -05:00
Telesphoreo a257fb2f9c Update to 1.19 2022-06-13 23:00:38 -05:00
Telesphoreo fc9924754a Update for 1.2 2022-06-05 22:13:20 -05:00
Telesphoreo 867e3ff79b Closes #8 2022-05-30 20:55:28 -05:00
Telesphoreo 8bef589b7a
Merge pull request #7 from plexusorg/content-type
Set Content-Type header to application/json for JSON endpoints (closes #6)
2022-05-30 02:44:29 -05:00
Allink 5a0ae8746c
Set Content-Type header to application/json for JSON endpoints (closes #6) 2022-05-30 03:55:03 +01:00
Telesphoreo 41a2475e3d Update version 2022-05-28 22:04:07 -05:00
Telesphoreo 0bcbc7e79c Update repo 2022-05-21 21:05:38 -05:00
Telesphoreo 95043a33db
Create CONTRIBUTING.md 2022-05-17 14:58:30 -05:00
Telesphoreo 93619dd05e
Update Bootstrap 2022-05-16 23:21:50 -05:00
Taah 244d027e5d fix this condition 2022-05-10 22:34:39 -07:00
Telesphoreo ceea975729
Add GitHub workflow 2022-05-08 14:00:41 -05:00
Telesphoreo 32f17a23f3
Update dependencies 2022-05-08 13:59:50 -05:00
Telesphoreo 1148ff5da9
Update README.md 2022-05-05 22:33:52 -05:00
Telesphoreo a0ae4a9720
i hate this 2022-04-24 17:24:38 -05:00
Telesphoreo 82238fa3ed
switch to server/api 2022-04-24 16:21:36 -05:00
Telesphoreo 6e152417b7 This needs to be changed 2022-04-21 20:24:43 -05:00
Telesphoreo 22bb27af24 Make it look nicer if you decide to view the source for some reason
Yes I know this is useless
2022-04-21 19:28:12 -05:00
Telesphoreo 2b24f0ee4c Absolutely based 2022-04-21 18:24:15 -05:00
Telesphoreo 7769f34f74 Back to snapshot 2022-04-20 22:01:54 -05:00
33 changed files with 671 additions and 259 deletions

18
.github/workflows/gradle.yml vendored Normal file
View File

@ -0,0 +1,18 @@
name: Gradle
on: [ push ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 17
uses: actions/setup-java@v2
with:
distribution: temurin
java-version: 17
cache: gradle
- name: Build with Gradle
run: chmod a+x gradlew && ./gradlew build --no-daemon

View File

@ -0,0 +1,49 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Plexus Code Style" version="1">
<JavaCodeStyleSettings>
<option name="CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND" value="20" />
<option name="IMPORT_LAYOUT_TABLE">
<value>
<package name="" withSubpackages="true" static="false" />
<package name="" withSubpackages="true" static="true" />
</value>
</option>
</JavaCodeStyleSettings>
<JetCodeStyleSettings>
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
<value>
<package name="java.util" alias="false" withSubpackages="false" />
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
<package name="io.ktor" alias="false" withSubpackages="true" />
</value>
</option>
<option name="PACKAGES_IMPORT_LAYOUT">
<value>
<package name="" alias="false" withSubpackages="true" />
<package name="java" alias="false" withSubpackages="true" />
<package name="javax" alias="false" withSubpackages="true" />
<package name="kotlin" alias="false" withSubpackages="true" />
<package name="" alias="true" withSubpackages="true" />
</value>
</option>
</JetCodeStyleSettings>
<codeStyleSettings language="JAVA">
<option name="BRACE_STYLE" value="2" />
<option name="CLASS_BRACE_STYLE" value="2" />
<option name="METHOD_BRACE_STYLE" value="2" />
<option name="LAMBDA_BRACE_STYLE" value="2" />
<option name="ELSE_ON_NEW_LINE" value="true" />
<option name="WHILE_ON_NEW_LINE" value="true" />
<option name="CATCH_ON_NEW_LINE" value="true" />
<option name="FINALLY_ON_NEW_LINE" value="true" />
<option name="SPACE_AFTER_TYPE_CAST" value="true" />
<option name="IF_BRACE_FORCE" value="3" />
<option name="DOWHILE_BRACE_FORCE" value="3" />
<option name="WHILE_BRACE_FORCE" value="3" />
<option name="FOR_BRACE_FORCE" value="3" />
<option name="BLANK_LINES_AFTER_CLASS_HEADER" value="0" />
<option name="KEEP_BLANK_LINES_BEFORE_RBRACE" value="1" />
<option name="KEEP_FIRST_COLUMN_COMMENT" value="false" />
</codeStyleSettings>
</code_scheme>
</component>

View File

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Plexus Code Style" />
</state>
</component>

24
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,24 @@
For those who are wanting to contribute, we fully encourage doing so. There are a few rules we require following when contributing however.
## Steps
1. Make an issue and get feedback. It's important to know if your idea will be accepted before writing any code.
- If it is a feature request, describe the feature and be extremely specific.
- If it is a bug report, ensure you include how to reproduce the bug and the expected outcome
- If it is an enhancement, describe your proposed changes. Ensure you are extremely specific.
2. Fork this project
3. Create a new branch that describes the new feature, enhancement, or bug fix. For example, this is good: `feature/add-xyz`. This is bad: `fix-this-lol`.
4. Write the code that addresses your change.
- Keep in mind that it **must** be formatted correctly. If you are using IntelliJ, there is a `codeStyle.xml` file that tells IntelliJ how to format your code. Check this link for information on how to use the file: https://www.jetbrains.com/help/idea/configuring-code-style.html#import-export-schemes
- If you are not using IntelliJ, that is fine. We use the Plexus Code Style (which is almost the same as Allman) so please format your code accordingly.
6. Push your changes to your new branch and make a PR based off of that branch.
## Requirements for a PR
- The issue must be marked as approved
- It must only address each specific issue. Don't make one PR for multiple issues.
- Your PR must compile and work. If it does not compile or work, your PR will most likely be rejected.
## Code requirements
- Most importantly, your code must be efficient. Your pull request may be rejected if your code is deemed inefficient or sloppy.
- Do not repeat yourself. Create functions as needed if you're using large blocks of code over and over again.
- Do not use an excessive amount of commits when making your PR. It makes the master branch look messy.
- Your code must be consistent with Plex's codebase. If a function already exists, use it.

View File

@ -1,2 +1,2 @@
# Module-HTTPD # Module-HTTPD [![Build Status](https://ci.plex.us.org/job/Module-HTTPD/badge/icon)](https://ci.plex.us.org/job/Module-HTTPD/)
The HTTPD module for Plex The HTTPD module for Plex

View File

@ -5,71 +5,75 @@ plugins {
} }
group = "dev.plex" group = "dev.plex"
version = "1.0.2" version = "1.4-SNAPSHOT"
description = "Module-HTTPD" description = "Module-HTTPD"
repositories { repositories {
mavenCentral() mavenCentral()
maven { maven {
url = uri("https://papermc.io/repo/repository/maven-public/") url = uri("https://repo.papermc.io/repository/maven-public/")
} }
maven { maven {
url = uri("https://nexus.telesphoreo.me/repository/plex/") url = uri("https://nexus.telesphoreo.me/repository/plex/")
} }
maven {
url = uri("https://nexus.telesphoreo.me/repository/totalfreedom/")
}
maven { maven {
url = uri("https://jitpack.io") url = uri("https://jitpack.io")
content {
includeGroup("com.github.MilkBowl")
}
} }
} }
dependencies { dependencies {
implementation("org.projectlombok:lombok:1.18.22") implementation("org.projectlombok:lombok:1.18.30")
annotationProcessor("org.projectlombok:lombok:1.18.22") annotationProcessor("org.projectlombok:lombok:1.18.30")
implementation("io.papermc.paper:paper-api:1.18.2-R0.1-SNAPSHOT") implementation("io.papermc.paper:paper-api:1.20.4-R0.1-SNAPSHOT")
implementation("dev.plex:Plex:1.0.2-SNAPSHOT") implementation("dev.plex:server:1.4-SNAPSHOT")
implementation("org.json:json:20220320") implementation("org.json:json:20231013")
implementation("org.reflections:reflections:0.10.2") implementation("org.reflections:reflections:0.10.2")
implementation("org.eclipse.jetty:jetty-server:11.0.9") implementation("org.eclipse.jetty:jetty-server:11.0.19")
implementation("org.eclipse.jetty:jetty-servlet:11.0.9") implementation("org.eclipse.jetty:jetty-servlet:11.0.19")
implementation("org.eclipse.jetty:jetty-proxy:11.0.9") implementation("org.eclipse.jetty:jetty-proxy:11.0.19")
implementation("com.github.MilkBowl:VaultAPI:1.7") implementation("com.github.MilkBowl:VaultAPI:1.7.1") {
exclude("org.bukkit", "bukkit")
}
implementation(platform("com.intellectualsites.bom:bom-newest:1.40")) // Ref: https://github.com/IntellectualSites/bom
compileOnly("com.fastasyncworldedit:FastAsyncWorldEdit-Core")
implementation("commons-io:commons-io:2.15.1")
}
tasks.getByName<Jar>("jar") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveBaseName.set("Module-HTTPD")
archiveVersion.set("")
from("src/main/resources") {
exclude("dev/**")
}
}
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
} }
tasks { tasks {
compileJava { compileJava {
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything options.encoding = Charsets.UTF_8.name()
// Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable.
// See https://openjdk.java.net/jeps/247 for more information.
options.release.set(17)
} }
javadoc { javadoc {
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything options.encoding = Charsets.UTF_8.name()
} }
processResources { processResources {
filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything filteringCharset = Charsets.UTF_8.name()
} }
} }
publishing { publishing {
publications { publications {
create<MavenPublication>("maven") { create<MavenPublication>("maven") {
from(components["java"]) from(components["java"])
} }
} }
}
tasks.getByName<Jar>("jar") {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveBaseName.set("Plex-HTTPD")
archiveVersion.set("")
from("src/main/resources") {
exclude("dev/**")
}
} }

Binary file not shown.

View File

@ -1,5 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists

31
gradlew vendored
View File

@ -55,7 +55,7 @@
# Darwin, MinGW, and NonStop. # Darwin, MinGW, and NonStop.
# #
# (3) This script is generated from the Groovy template # (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project. # within the Gradle project.
# #
# You can find Gradle at https://github.com/gradle/gradle/. # You can find Gradle at https://github.com/gradle/gradle/.
@ -80,13 +80,11 @@ do
esac esac
done done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # This is normally unused
# shellcheck disable=SC2034
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/} APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value. # Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum MAX_FD=maximum
@ -133,22 +131,29 @@ location of your Java installation."
fi fi
else else
JAVACMD=java JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the Please set the JAVA_HOME variable in your environment to match the
location of your Java installation." location of your Java installation."
fi
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #( case $MAX_FD in #(
max*) max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) || MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit" warn "Could not query maximum file descriptor limit"
esac esac
case $MAX_FD in #( case $MAX_FD in #(
'' | soft) :;; #( '' | soft) :;; #(
*) *)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" || ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD" warn "Could not set maximum file descriptor limit to $MAX_FD"
esac esac
@ -193,6 +198,10 @@ if "$cygwin" || "$msys" ; then
done done
fi fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command; # Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in # shell script including quotes and variable substitutions, so put them in
@ -205,6 +214,12 @@ set -- \
org.gradle.wrapper.GradleWrapperMain \ org.gradle.wrapper.GradleWrapperMain \
"$@" "$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args. # Use "xargs" to parse quoted args.
# #
# With -n1 it outputs one arg per line, with the quotes and backslashes removed. # With -n1 it outputs one arg per line, with the quotes and backslashes removed.

15
gradlew.bat vendored
View File

@ -14,7 +14,7 @@
@rem limitations under the License. @rem limitations under the License.
@rem @rem
@if "%DEBUG%" == "" @echo off @if "%DEBUG%"=="" @echo off
@rem ########################################################################## @rem ##########################################################################
@rem @rem
@rem Gradle startup script for Windows @rem Gradle startup script for Windows
@ -25,7 +25,8 @@
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@ -40,7 +41,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1 %JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute if %ERRORLEVEL% equ 0 goto execute
echo. echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
:end :end
@rem End local scope for the variables with windows NT shell @rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd if %ERRORLEVEL% equ 0 goto mainEnd
:fail :fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code! rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 set EXIT_CODE=%ERRORLEVEL%
exit /b 1 if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd :mainEnd
if "%OS%"=="Windows_NT" endlocal if "%OS%"=="Windows_NT" endlocal

View File

@ -1,5 +1,6 @@
package dev.plex; package dev.plex;
import dev.plex.authentication.AuthenticationManager;
import dev.plex.cache.FileCache; import dev.plex.cache.FileCache;
import dev.plex.config.ModuleConfig; import dev.plex.config.ModuleConfig;
import dev.plex.module.PlexModule; import dev.plex.module.PlexModule;
@ -7,25 +8,19 @@ import dev.plex.request.AbstractServlet;
import dev.plex.request.SchematicUploadServlet; import dev.plex.request.SchematicUploadServlet;
import dev.plex.request.impl.*; import dev.plex.request.impl.*;
import dev.plex.util.PlexLog; import dev.plex.util.PlexLog;
import java.io.File;
import java.util.concurrent.atomic.AtomicReference;
import jakarta.servlet.MultipartConfigElement; import jakarta.servlet.MultipartConfigElement;
import lombok.Getter; import lombok.Getter;
import net.milkbowl.vault.permission.Permission; import net.milkbowl.vault.permission.Permission;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.RegisteredServiceProvider;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlet.ServletHolder;
import java.io.File;
import java.util.concurrent.atomic.AtomicReference;
public class HTTPDModule extends PlexModule public class HTTPDModule extends PlexModule
{ {
public static ServletContextHandler context; public static ServletContextHandler context;
@ -41,6 +36,8 @@ public class HTTPDModule extends PlexModule
public static final String template = AbstractServlet.readFileReal(HTTPDModule.class.getResourceAsStream("/httpd/template.html")); public static final String template = AbstractServlet.readFileReal(HTTPDModule.class.getResourceAsStream("/httpd/template.html"));
private AuthenticationManager authenticationManager;
@Override @Override
public void load() public void load()
{ {
@ -53,10 +50,22 @@ public class HTTPDModule extends PlexModule
{ {
moduleConfig.load(); moduleConfig.load();
PlexLog.debug("HTTPD Module Port: {0}", moduleConfig.getInt("server.port")); PlexLog.debug("HTTPD Module Port: {0}", moduleConfig.getInt("server.port"));
if (!setupPermissions() && getPlex().getSystem().equalsIgnoreCase("permissions") && !Bukkit.getPluginManager().isPluginEnabled("Vault")) if ((!Bukkit.getPluginManager().isPluginEnabled("Vault") || !setupPermissions()))
{ {
throw new RuntimeException("Plex-HTTPD requires the 'Vault' plugin as well as a Permissions plugin that hooks into 'Vault'. We recommend LuckPerms!"); throw new RuntimeException("Plex-HTTPD requires the 'Vault' plugin as well as a Permissions plugin that hooks into 'Vault'. We recommend LuckPerms!");
} }
this.authenticationManager = new AuthenticationManager();
if (this.authenticationManager.provider() != null)
{
PlexLog.debug(this.authenticationManager.provider().generateLogin());
}
else
{
PlexLog.debug("Provider was not found for Authentication so disabled");
}
serverThread = new Thread(() -> serverThread = new Thread(() ->
{ {
Server server = new Server(); Server server = new Server();
@ -70,18 +79,21 @@ public class HTTPDModule extends PlexModule
connector.setHost(moduleConfig.getString("server.bind-address")); connector.setHost(moduleConfig.getString("server.bind-address"));
connector.setPort(moduleConfig.getInt("server.port")); connector.setPort(moduleConfig.getInt("server.port"));
new AdminsEndpoint();
new IndefBansEndpoint(); new IndefBansEndpoint();
new IndexEndpoint(); new IndexEndpoint();
new ListEndpoint(); new ListEndpoint();
new PunishmentsEndpoint(); new PunishmentsEndpoint();
new CommandsEndpoint();
new SchematicDownloadEndpoint(); new SchematicDownloadEndpoint();
new SchematicUploadEndpoint(); new SchematicUploadEndpoint();
ServletHolder uploadHolder = HTTPDModule.context.addServlet(SchematicUploadServlet.class, "/api/schematics/uploading"); ServletHolder uploadHolder = HTTPDModule.context.addServlet(SchematicUploadServlet.class, "/api/schematics/uploading");
File uploadLoc = new File(System.getProperty("java.io.tmpdir"), "schematic-temp-dir"); File uploadLoc = new File(System.getProperty("java.io.tmpdir"), "schematic-temp-dir");
if (!uploadLoc.exists()) uploadLoc.mkdirs(); if (!uploadLoc.exists())
{
uploadLoc.mkdirs();
}
uploadHolder.getRegistration().setMultipartConfig(new MultipartConfigElement(uploadLoc.getAbsolutePath(), 1024 * 1024 * 5, 1024 * 1024 * 25, 1024 * 1024)); uploadHolder.getRegistration().setMultipartConfig(new MultipartConfigElement(uploadLoc.getAbsolutePath(), 1024 * 1024 * 5, 1024 * 1024 * 25, 1024 * 1024));
server.setConnectors(new Connector[]{connector}); server.setConnectors(new Connector[]{connector});
@ -140,7 +152,7 @@ public class HTTPDModule extends PlexModule
} }
} }
private static boolean isFileSystemCaseSensitive = !new File( "a" ).equals( new File( "A" ) ); private static boolean isFileSystemCaseSensitive = !new File("a").equals(new File("A"));
public static boolean fileNameEquals(String filename1, String filename2) public static boolean fileNameEquals(String filename1, String filename2)
{ {

View File

@ -0,0 +1,24 @@
package dev.plex.authentication;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.ZonedDateTime;
import java.util.LinkedList;
import java.util.List;
/**
* @author Taah
* @since 6:37 PM [03-05-2024]
*/
@Data
@Accessors(fluent = true)
public class AuthenticatedUser
{
private final String ip;
private final ZonedDateTime lastAuthenticated;
private final LinkedList<String> roles = Lists.newLinkedList();
private final UserType userType = UserType.UNKNOWN;
}

View File

@ -0,0 +1,57 @@
package dev.plex.authentication;
import dev.plex.HTTPDModule;
import dev.plex.authentication.impl.DiscordOAuth2Provider;
import dev.plex.util.PlexLog;
import org.apache.commons.lang3.NotImplementedException;
/**
* @author Taah
* @since 7:08 PM [03-05-2024]
*/
public class AuthenticationManager
{
private final OAuth2Provider provider;
public AuthenticationManager()
{
final boolean enabled = HTTPDModule.moduleConfig.getBoolean("authentication.enabled", false);
if (!enabled)
{
provider = null;
return;
}
PlexLog.debug("[HTTPD] Auth is enabled");
final String providerName = HTTPDModule.moduleConfig.getString("authentication.provider.name", "");
if (providerName.isEmpty())
{
PlexLog.error("OAuth2 Authentication is enabled but no provider was given!");
provider = null;
return;
}
PlexLog.debug("[HTTPD] Provider name is {0}", providerName);
switch (providerName.toLowerCase())
{
case "discord" -> {
provider = new DiscordOAuth2Provider();
}
case "xenforo" -> {
throw new NotImplementedException("XenForo OAuth2 is not implemented yet!");
}
default -> {
provider = null;
}
}
PlexLog.log("Using {0} provider for authentication", providerName);
}
public OAuth2Provider provider()
{
return this.provider;
}
}

View File

@ -0,0 +1,21 @@
package dev.plex.authentication;
import org.eclipse.jetty.server.Response;
import java.util.HashMap;
/**
* @author Taah
* @since 6:36 PM [03-05-2024]
*/
public interface OAuth2Provider
{
HashMap<String, AuthenticatedUser> sessions();
AuthenticatedUser login(Response response, UserType type);
String[] roles(AuthenticatedUser user);
String generateLogin();
}

View File

@ -0,0 +1,10 @@
package dev.plex.authentication;
/**
* @author Taah
* @since 6:37 PM [03-05-2024]
*/
public enum UserType
{
DISCORD, UNKNOWN
}

View File

@ -0,0 +1,81 @@
package dev.plex.authentication.impl;
import com.google.common.collect.Maps;
import dev.plex.HTTPDModule;
import dev.plex.authentication.AuthenticatedUser;
import dev.plex.authentication.OAuth2Provider;
import dev.plex.authentication.UserType;
import dev.plex.util.PlexLog;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.server.Response;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
/**
* @author Taah
* @since 6:41 PM [03-05-2024]
*/
public class DiscordOAuth2Provider implements OAuth2Provider
{
private final HashMap<String, AuthenticatedUser> sessions = Maps.newHashMap();
private final String token;
private final String clientId;
private final String redirectUri;
public DiscordOAuth2Provider()
{
token = System.getenv("BOT_TOKEN").isEmpty() ? HTTPDModule.moduleConfig.getString("authentication.provider.discord.token", System.getProperty("BOT_TOKEN", "")) : System.getenv("BOT_TOKEN");
clientId = HTTPDModule.moduleConfig.getString("authentication.provider.discord.clientId", "");
redirectUri = URLEncoder.encode(HTTPDModule.moduleConfig.getString("authentication.provider.redirectUri", ""), StandardCharsets.UTF_8);
PlexLog.debug("[HTTPD] Client ID: {0}, Redirect URL: {1}", clientId, redirectUri);
if (redirectUri.isEmpty())
{
PlexLog.error("Provided authentication redirect url was empty for HTTPD!");
return;
}
if (token.isEmpty())
{
PlexLog.error("Provided discord authentication token was empty for HTTPD!");
return;
}
if (clientId.isEmpty())
{
PlexLog.error("Provided discord client ID was empty for HTTPD!");
}
}
@Override
public HashMap<String, AuthenticatedUser> sessions()
{
return sessions;
}
@Override
public AuthenticatedUser login(Response response, UserType type)
{
return null;
}
@Override
public String[] roles(AuthenticatedUser user)
{
return new String[0];
}
@Override
public String generateLogin()
{
return String.format("https://discord.com/oauth2/authorize?client_id=%s&scope=%s&redirect_uri=%s",
clientId,
"identify%20guilds%20guilds.members.read",
redirectUri);
}
}

View File

@ -98,6 +98,7 @@ public class AbstractServlet extends HttpServlet
base = base.replace("${ACTIVE_ADMINS}", ""); base = base.replace("${ACTIVE_ADMINS}", "");
base = base.replace("${ACTIVE_INDEFBANS}", ""); base = base.replace("${ACTIVE_INDEFBANS}", "");
base = base.replace("${ACTIVE_LIST}", ""); base = base.replace("${ACTIVE_LIST}", "");
base = base.replace("${ACTIVE_COMMANDS}", "");
base = base.replace("${ACTIVE_PUNISHMENTS}", ""); base = base.replace("${ACTIVE_PUNISHMENTS}", "");
base = base.replace("${ACTIVE_SCHEMATICS}", ""); base = base.replace("${ACTIVE_SCHEMATICS}", "");
base = base.replace("${CONTENT}", info[2]); base = base.replace("${CONTENT}", info[2]);

View File

@ -1,26 +1,27 @@
package dev.plex.request; package dev.plex.request;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormat;
import com.sk89q.worldedit.extent.clipboard.io.ClipboardFormats;
import dev.plex.HTTPDModule; import dev.plex.HTTPDModule;
import dev.plex.Plex;
import dev.plex.cache.DataUtils; import dev.plex.cache.DataUtils;
import dev.plex.player.PlexPlayer; import dev.plex.player.PlexPlayer;
import dev.plex.rank.enums.Rank;
import dev.plex.util.PlexLog; import dev.plex.util.PlexLog;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet; import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part; import jakarta.servlet.http.Part;
import java.io.File; import org.apache.commons.io.FileUtils;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.Arrays;
import java.util.regex.Pattern;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.regex.Pattern;
public class SchematicUploadServlet extends HttpServlet public class SchematicUploadServlet extends HttpServlet
{ {
private static final Pattern schemNameMatcher = Pattern.compile("^[a-z0-9'!,_ -]{1,30}\\.schem(atic)?$", Pattern.CASE_INSENSITIVE); private static final Pattern schemNameMatcher = Pattern.compile("^[a-z0-9'!,_ -]{1,30}\\.schem(atic)?$", Pattern.CASE_INSENSITIVE);
@ -39,24 +40,12 @@ public class SchematicUploadServlet extends HttpServlet
response.getWriter().println(schematicUploadBadHTML("Couldn't load your IP Address: " + request.getRemoteAddr() + ". Have you joined the server before?")); response.getWriter().println(schematicUploadBadHTML("Couldn't load your IP Address: " + request.getRemoteAddr() + ". Have you joined the server before?"));
return; return;
} }
if (Plex.get().getSystem().equalsIgnoreCase("ranks")) PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(plexPlayer.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.schematics.upload"))
{ {
PlexLog.debug("Plex-HTTPD using ranks check"); response.getWriter().println(schematicUploadBadHTML("You do not have permission to upload schematics."));
if (!plexPlayer.getRankFromString().isAtLeast(Rank.ADMIN)) return;
{
response.getWriter().println(schematicUploadBadHTML("You must be an admin or above to upload schematics."));
return;
}
}
else if (Plex.get().getSystem().equalsIgnoreCase("permissions"))
{
PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(plexPlayer.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.schematics.upload"))
{
response.getWriter().println(schematicUploadBadHTML("You do not have permission to upload schematics."));
return;
}
} }
File worldeditFolder = HTTPDModule.getWorldeditFolder(); File worldeditFolder = HTTPDModule.getWorldeditFolder();
if (worldeditFolder == null) if (worldeditFolder == null)
@ -88,9 +77,31 @@ public class SchematicUploadServlet extends HttpServlet
return; return;
} }
InputStream inputStream = uploadPart.getInputStream(); InputStream inputStream = uploadPart.getInputStream();
Files.copy(inputStream, new File(worldeditFolder, filename).toPath(), StandardCopyOption.REPLACE_EXISTING); File schematicFile = new File(worldeditFolder, filename);
FileUtils.copyInputStreamToFile(inputStream, schematicFile);
ClipboardFormat schematicFormat = ClipboardFormats.findByFile(schematicFile);
if (schematicFormat == null)
{
PlexLog.log("IP Address: " + request.getRemoteAddr() + " FAILED to upload schematic with filename: " + filename);
response.getWriter().println(schematicUploadBadHTML("Schematic is not a valid format."));
FileUtils.deleteQuietly(schematicFile);
return;
}
try
{
schematicFormat.getReader(new FileInputStream(schematicFile));
}
catch (IOException e)
{
PlexLog.log("IP Address: " + request.getRemoteAddr() + " FAILED to upload schematic with filename: " + filename);
response.getWriter().println(schematicUploadBadHTML("Schematic is not a valid format."));
FileUtils.deleteQuietly(schematicFile);
return;
}
// Files.copy(inputStream, schematic.toPath(), StandardCopyOption.REPLACE_EXISTING);
inputStream.close(); inputStream.close();
response.getWriter().println(schematicUploadGoodHTML("Successfully uploaded <b>" + filename + ".")); response.getWriter().println(schematicUploadGoodHTML("Successfully uploaded <b>" + filename + "</b>."));
PlexLog.log("IP Address: " + request.getRemoteAddr() + " uploaded schematic with filename: " + filename);
} }
private String schematicUploadBadHTML(String message) private String schematicUploadBadHTML(String message)

View File

@ -1,69 +0,0 @@
package dev.plex.request.impl;
import com.google.common.collect.Lists;
import com.google.gson.GsonBuilder;
import dev.plex.HTTPDModule;
import dev.plex.Plex;
import dev.plex.cache.DataUtils;
import dev.plex.player.PlexPlayer;
import dev.plex.rank.enums.Rank;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog;
import dev.plex.util.adapter.ZonedDateTimeSerializer;
import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.util.stream.Collectors;
import jakarta.servlet.http.HttpServletResponse;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
public class AdminsEndpoint extends AbstractServlet
{
private static final String TITLE = "Admins - Plex HTTPD";
@GetMapping(endpoint = "/api/admins/")
public String getAdmins(HttpServletRequest request, HttpServletResponse response)
{
String ipAddress = request.getRemoteAddr();
if (ipAddress == null)
{
return adminsHTML("An IP address could not be detected. Please ensure you are connecting using IPv4.");
}
final PlexPlayer player = DataUtils.getPlayerByIP(ipAddress);
if (player == null)
{
// This likely means they've never joined the server before. That's okay. We can just not return IPs.
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeSerializer()).setPrettyPrinting().create().toJson(Plex.get().getAdminList().getAllAdminPlayers().stream().peek(plexPlayer -> plexPlayer.setIps(Lists.newArrayList())).peek(plexPlayer -> plexPlayer.setPunishments(Lists.newArrayList())).collect(Collectors.toList()));
}
if (Plex.get().getSystem().equalsIgnoreCase("ranks"))
{
PlexLog.debug("Plex-HTTPD using ranks check");
if (!player.getRankFromString().isAtLeast(Rank.ADMIN))
{
// Don't return IPs either if the person is not an Admin or above.
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeSerializer()).setPrettyPrinting().create().toJson(Plex.get().getAdminList().getAllAdminPlayers().stream().peek(plexPlayer -> plexPlayer.setIps(Lists.newArrayList())).peek(plexPlayer -> plexPlayer.setPunishments(Lists.newArrayList())).collect(Collectors.toList()));
}
}
else if (Plex.get().getSystem().equalsIgnoreCase("permissions"))
{
PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.admins.access"))
{
// If the person doesn't have permission, don't return IPs
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeSerializer()).setPrettyPrinting().create().toJson(Plex.get().getAdminList().getAllAdminPlayers().stream().peek(plexPlayer -> plexPlayer.setIps(Lists.newArrayList())).peek(plexPlayer -> plexPlayer.setPunishments(Lists.newArrayList())).collect(Collectors.toList()));
}
}
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeSerializer()).setPrettyPrinting().create().toJson(Plex.get().getAdminList().getAllAdminPlayers());
}
private String adminsHTML(String message)
{
String file = readFile(this.getClass().getResourceAsStream("/httpd/admins.html"));
file = file.replace("${MESSAGE}", message);
return file;
}
}

View File

@ -0,0 +1,27 @@
package dev.plex.request.impl;
import dev.plex.command.PlexCommand;
import dev.plex.command.annotation.CommandPermissions;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.command.PluginIdentifiableCommand;
import java.util.*;
public class AuthenticationEndpoint extends AbstractServlet
{
@GetMapping(endpoint = "/oauth2")
public String login(HttpServletRequest request, HttpServletResponse response)
{
// TODO: Nuh uh
return "";
}
}

View File

@ -0,0 +1,111 @@
package dev.plex.request.impl;
import dev.plex.command.PlexCommand;
import dev.plex.command.annotation.CommandPermissions;
import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.SortedMap;
import java.util.TreeMap;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.command.PluginIdentifiableCommand;
public class CommandsEndpoint extends AbstractServlet
{
private final StringBuilder list = new StringBuilder();
private boolean loadedCommands = false;
@GetMapping(endpoint = "/api/commands/")
public String getCommands(HttpServletRequest request, HttpServletResponse response)
{
if (!loadedCommands)
{
final SortedMap<String, List<Command>> commandMap = new TreeMap<>();
final CommandMap map = Bukkit.getCommandMap();
for (Command command : map.getKnownCommands().values())
{
String plugin = "Bukkit";
if (command instanceof PluginIdentifiableCommand)
{
plugin = ((PluginIdentifiableCommand) command).getPlugin().getName();
}
List<Command> pluginCommands = commandMap.computeIfAbsent(plugin, k -> new ArrayList<>());
if (!pluginCommands.contains(command))
{
pluginCommands.add(command);
}
}
for (String key : commandMap.keySet())
{
commandMap.get(key).sort(Comparator.comparing(Command::getName));
StringBuilder rows = new StringBuilder();
for (Command command : commandMap.get(key))
{
String permission = command.getPermission();
if (command instanceof PlexCommand plexCmd)
{
CommandPermissions perms = plexCmd.getClass().getAnnotation(CommandPermissions.class);
if (perms != null)
{
permission = (perms.permission().isBlank() ? "N/A" : perms.permission());
}
}
rows.append(createRow(command.getName(), command.getAliases(), command.getDescription(), command.getUsage(), permission));
}
list.append(createTable(key, rows.toString())).append("\n");
}
loadedCommands = true;
}
return commandsHTML(list.toString());
}
private String commandsHTML(String commandsList)
{
String file = readFile(this.getClass().getResourceAsStream("/httpd/commands.html"));
file = file.replace("${commands}", commandsList);
return file;
}
private String createTable(String pluginName, String commandRows)
{
return "<details id=\"" + pluginName + "\"><summary>" + pluginName + "</summary>\n"
+ "<table id=\"" + pluginName + "Table\" class=\"table table-striped table-bordered\">\n"
+ " <thead>\n <tr>\n <th scope=\"col\">Name (Aliases)</th>\n "
+ "<th scope=\"col\">Description</th>\n "
+ "<th scope=\"col\">Usage</th>\n "
+ "<th scope=\"col\">Permission</th>\n </tr>\n</thead>\n"
+ "<tbody>\n " + commandRows + "\n</tbody>\n</table>\n</details>";
}
private String createRow(String name, List<String> aliases, String description, String usage, String permission)
{
return " <tr>\n <th scope=\"row\">" + name
+ (aliases.isEmpty() || aliases.toString().equals("[]") ? "" : " (" + String.join(", ", aliases) + ")") + "</th>\n"
+ " <th scope=\"row\">" + description + "</th>\n"
+ " <th scope=\"row\"><code>" + cleanUsage(usage) + "</code></th>\n"
+ " <th scope=\"row\">" + (permission != null ? permission.replaceAll(";", "<br>") : "N/A") + "</th>\n </tr>";
}
private String cleanUsage(String usage)
{
usage = usage.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
if (usage.isBlank())
{
usage = "Not Provided";
}
return usage.startsWith("/") || usage.equals("Not Provided") ? usage : "/" + usage;
}
}

View File

@ -5,7 +5,6 @@ import dev.plex.HTTPDModule;
import dev.plex.Plex; import dev.plex.Plex;
import dev.plex.cache.DataUtils; import dev.plex.cache.DataUtils;
import dev.plex.player.PlexPlayer; import dev.plex.player.PlexPlayer;
import dev.plex.rank.enums.Rank;
import dev.plex.request.AbstractServlet; import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping; import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog; import dev.plex.util.PlexLog;
@ -31,23 +30,14 @@ public class IndefBansEndpoint extends AbstractServlet
{ {
return indefbansHTML("Couldn't load your IP Address: " + ipAddress + ". Have you joined the server before?"); return indefbansHTML("Couldn't load your IP Address: " + ipAddress + ". Have you joined the server before?");
} }
if (Plex.get().getSystem().equalsIgnoreCase("ranks")) PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.indefbans.access"))
{ {
PlexLog.debug("Plex-HTTPD using ranks check"); return indefbansHTML("Not enough permissions to view this page.");
if (!player.getRankFromString().isAtLeast(Rank.ADMIN))
{
return indefbansHTML("Not a high enough rank to view this page.");
}
}
else if (Plex.get().getSystem().equalsIgnoreCase("permissions"))
{
PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.indefbans.access"))
{
return indefbansHTML("Not enough permissions to view this page.");
}
} }
response.setHeader("content-type", "application/json");
return new GsonBuilder().setPrettyPrinting().create().toJson(Plex.get().getPunishmentManager().getIndefiniteBans().stream().toList()); return new GsonBuilder().setPrettyPrinting().create().toJson(Plex.get().getPunishmentManager().getIndefiniteBans().stream().toList());
} }

View File

@ -3,6 +3,7 @@ package dev.plex.request.impl;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import dev.plex.request.AbstractServlet; import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping; import dev.plex.request.GetMapping;
import dev.plex.request.MappingHeaders;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -14,6 +15,7 @@ import org.bukkit.entity.Player;
public class ListEndpoint extends AbstractServlet public class ListEndpoint extends AbstractServlet
{ {
@GetMapping(endpoint = "/api/list/") @GetMapping(endpoint = "/api/list/")
@MappingHeaders(headers = "content-type;application/json")
public String getOnlinePlayers(HttpServletRequest request, HttpServletResponse response) public String getOnlinePlayers(HttpServletRequest request, HttpServletResponse response)
{ {
List<String> players = new ArrayList<>(); List<String> players = new ArrayList<>();

View File

@ -2,20 +2,15 @@ package dev.plex.request.impl;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import dev.plex.HTTPDModule; import dev.plex.HTTPDModule;
import dev.plex.Plex;
import dev.plex.cache.DataUtils; import dev.plex.cache.DataUtils;
import dev.plex.player.PlexPlayer; import dev.plex.player.PlexPlayer;
import dev.plex.rank.enums.Rank;
import dev.plex.request.AbstractServlet; import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping; import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog; import dev.plex.util.adapter.ZonedDateTimeAdapter;
import dev.plex.util.adapter.ZonedDateTimeSerializer;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import java.time.LocalDateTime; import jakarta.servlet.http.HttpServletResponse;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.UUID; import java.util.UUID;
import jakarta.servlet.http.HttpServletResponse;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
@ -59,28 +54,17 @@ public class PunishmentsEndpoint extends AbstractServlet
if (player == null) if (player == null)
{ {
// If the player is null, give it to them without the IPs // If the player is null, give it to them without the IPs
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeSerializer()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().peek(punishment -> punishment.setIp("")).toList()); return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().peek(punishment -> punishment.setIp("")).toList());
} }
if (Plex.get().getSystem().equalsIgnoreCase("ranks")) final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.punishments.access"))
{ {
PlexLog.debug("Plex-HTTPD using ranks check"); // If the person doesn't have permission, don't return IPs
if (!player.getRankFromString().isAtLeast(Rank.ADMIN)) return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().peek(punishment -> punishment.setIp("")).toList());
{
// Don't return IPs either if the person is not an Admin or above.
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeSerializer()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().peek(punishment -> punishment.setIp("")).toList());
}
} }
else if (Plex.get().getSystem().equalsIgnoreCase("permissions"))
{ response.setHeader("content-type", "application/json");
PlexLog.debug("Plex-HTTPD using permissions check"); return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeAdapter()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().toList());
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.punishments.access"))
{
// If the person doesn't have permission, don't return IPs
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeSerializer()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().peek(punishment -> punishment.setIp("")).toList());
}
}
return new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, new ZonedDateTimeSerializer()).setPrettyPrinting().create().toJson(punishedPlayer.getPunishments().stream().toList());
} }
private String punishmentsHTML(String message) private String punishmentsHTML(String message)

View File

@ -3,15 +3,21 @@ package dev.plex.request.impl;
import dev.plex.HTTPDModule; import dev.plex.HTTPDModule;
import dev.plex.request.AbstractServlet; import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping; import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
public class SchematicDownloadEndpoint extends AbstractServlet public class SchematicDownloadEndpoint extends AbstractServlet
{ {
List<File> files = new ArrayList<>();
@GetMapping(endpoint = "/api/schematics/download/") @GetMapping(endpoint = "/api/schematics/download/")
public String downloadSchematic(HttpServletRequest request, HttpServletResponse response) public String downloadSchematic(HttpServletRequest request, HttpServletResponse response)
{ {
@ -72,20 +78,35 @@ public class SchematicDownloadEndpoint extends AbstractServlet
return null; return null;
} }
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
File[] alphabetical = worldeditFolder.listFiles(); for (File worldeditFile : listFilesForFolder(worldeditFolder))
if (alphabetical != null)
{ {
Arrays.sort(alphabetical); String fixedPath = worldeditFile.getPath().replace("plugins/FastAsyncWorldEdit/schematics/", "");
for (File worldeditFile : alphabetical) fixedPath = fixedPath.replace("plugins/WorldEdit/schematics/", "");
{ String sanitizedName = fixedPath.replaceAll("<", "&lt;").replaceAll(">", "&gt;");
String sanitizedName = worldeditFile.getName().replaceAll("<","&lt;").replaceAll(">","&gt;"); sb.append(" <tr>\n" +
sb.append("<tr>" + " <th scope=\"row\">\n <a href=\"" + fixedPath + "\" download>" + sanitizedName + "</a>\n </th>\n" +
"<th scope=\"row\"><a href=\"" + worldeditFile.getName() + "\" download>" + sanitizedName + "</a></th>" + " <td>\n " + formattedSize(worldeditFile.length()) + "\n </td>\n" +
"<td>" + formattedSize(worldeditFile.length()) + "</td>" + " </tr>\n");
"</tr>");
}
file = file.replace("${schematics}", sb.toString());
} }
file = file.replace("${schematics}", sb.toString());
files.clear();
return file; return file;
} }
public List<File> listFilesForFolder(final File folder)
{
for (File fileEntry : folder.listFiles())
{
if (fileEntry.isDirectory())
{
PlexLog.debug("Found directory");
listFilesForFolder(fileEntry);
}
else
{
files.add(fileEntry);
}
}
return files;
}
} }

View File

@ -1,10 +1,8 @@
package dev.plex.request.impl; package dev.plex.request.impl;
import dev.plex.HTTPDModule; import dev.plex.HTTPDModule;
import dev.plex.Plex;
import dev.plex.cache.DataUtils; import dev.plex.cache.DataUtils;
import dev.plex.player.PlexPlayer; import dev.plex.player.PlexPlayer;
import dev.plex.rank.enums.Rank;
import dev.plex.request.AbstractServlet; import dev.plex.request.AbstractServlet;
import dev.plex.request.GetMapping; import dev.plex.request.GetMapping;
import dev.plex.util.PlexLog; import dev.plex.util.PlexLog;
@ -28,22 +26,11 @@ public class SchematicUploadEndpoint extends AbstractServlet
{ {
return schematicsHTML("Couldn't load your IP Address: " + ipAddress + ". Have you joined the server before?"); return schematicsHTML("Couldn't load your IP Address: " + ipAddress + ". Have you joined the server before?");
} }
if (Plex.get().getSystem().equalsIgnoreCase("ranks")) PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.schematics.upload"))
{ {
PlexLog.debug("Plex-HTTPD using ranks check"); return schematicsHTML("You do not have permission to upload schematics.");
if (!player.getRankFromString().isAtLeast(Rank.ADMIN))
{
return schematicsHTML("You must be an admin or above to upload schematics.");
}
}
else if (Plex.get().getSystem().equalsIgnoreCase("permissions"))
{
PlexLog.debug("Plex-HTTPD using permissions check");
final OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(player.getUuid());
if (!HTTPDModule.getPermissions().playerHas(null, offlinePlayer, "plex.httpd.schematics.upload"))
{
return schematicsHTML("You do not have permission to upload schematics.");
}
} }
return readFile(this.getClass().getResourceAsStream("/httpd/schematic_upload.html")); return readFile(this.getClass().getResourceAsStream("/httpd/schematic_upload.html"));
} }

View File

@ -2,18 +2,21 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
<script type="text/javascript" <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/Telesphoreo/bootstrap-color-switcher@master/script.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@200;400&display=swap" rel="stylesheet">
<title>${TITLE} - Plex HTTPD</title> <title>${TITLE} - Plex HTTPD</title>
</head> </head>
<body> <body style="font-family: 'IBM Plex Sans', sans-serif;">
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="/">Plex HTTPD</a> <a class="navbar-brand" href="/" style="font-weight:200;">Plex HTTPD</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
@ -23,9 +26,6 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link ${ACTIVE_HOME}" href="/">Home</a> <a class="nav-link ${ACTIVE_HOME}" href="/">Home</a>
</li> </li>
<li class="nav-item">
<a class="nav-link ${ACTIVE_ADMINS}" href="/api/admins/">Admins</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link ${ACTIVE_INDEFBANS}" href="/api/indefbans/">Indefinite Bans</a> <a class="nav-link ${ACTIVE_INDEFBANS}" href="/api/indefbans/">Indefinite Bans</a>
</li> </li>

View File

@ -1,4 +0,0 @@
Admins
ADMINS
<h2>Admins</h2>
<h5 class="alert alert-danger mb-3 w-auto p-3" role="alert"><b>Error:</b> ${MESSAGE}</h5>

View File

@ -0,0 +1,12 @@
Commands
COMMANDS
<style>
summary {
font-size: 24px;
padding: 16px;
}
</style>
<h2>Commands List</h2>
<h5>A list of commands is below.</h5>
<br><br>
${commands}

View File

@ -1,4 +1,15 @@
server: server:
bind-address: 0.0.0.0 bind-address: 0.0.0.0
port: 27192 port: 27192
logging: false logging: false
authentication:
enabled: false
# Providers: discord
provider:
name: discord
redirectUri: ""
discord: # Fill if using discord provider
clientId: ""
token: "" # Can also use environment variable or system property BOT_TOKEN

View File

@ -15,7 +15,7 @@ SCHEMATICS
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
${schematics} ${schematics}
</tbody> </tbody>
</table> </table>
<script> <script>

View File

@ -2,18 +2,21 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous"> integrity="sha384-KK94CHFLLe+nY2dmCWGMq91rCGa5gtU4mk92HdvYe+M/SXH301p5ILy+dN9+nJOZ" crossorigin="anonymous">
<script type="text/javascript" <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js"
src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ENjdO4Dr2bkBIFxQpeoTz1HIcje39Wm4jDKdf19U8gI4ddQ3GYNS7NTKfAdVQSZe"
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
crossorigin="anonymous"></script> crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/gh/Telesphoreo/bootstrap-color-switcher@master/script.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@200;400&display=swap" rel="stylesheet">
<title>${TITLE} - Plex HTTPD</title> <title>${TITLE} - Plex HTTPD</title>
</head> </head>
<body> <body style="font-family: 'IBM Plex Sans', sans-serif;">
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg bg-body-tertiary">
<div class="container-fluid"> <div class="container-fluid">
<a class="navbar-brand" href="/">Plex HTTPD</a> <a class="navbar-brand" href="/" style="font-weight:200;">Plex HTTPD</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav"
aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation"> aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
@ -23,9 +26,6 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link ${ACTIVE_HOME}" href="/">Home</a> <a class="nav-link ${ACTIVE_HOME}" href="/">Home</a>
</li> </li>
<li class="nav-item">
<a class="nav-link ${ACTIVE_ADMINS}" href="/api/admins/">Admins</a>
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link ${ACTIVE_INDEFBANS}" href="/api/indefbans/">Indefinite Bans</a> <a class="nav-link ${ACTIVE_INDEFBANS}" href="/api/indefbans/">Indefinite Bans</a>
</li> </li>
@ -35,6 +35,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link ${ACTIVE_PUNISHMENTS}" href="/api/punishments/">Punishments</a> <a class="nav-link ${ACTIVE_PUNISHMENTS}" href="/api/punishments/">Punishments</a>
</li> </li>
<li>
<a class="nav-link ${ACTIVE_COMMANDS}" href="/api/commands/">Commands</a>
</li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle ${ACTIVE_SCHEMATICS}" id="navbarDropdownMenuLink" role="button" <a class="nav-link dropdown-toggle ${ACTIVE_SCHEMATICS}" id="navbarDropdownMenuLink" role="button"
data-bs-toggle="dropdown" aria-expanded="false"> data-bs-toggle="dropdown" aria-expanded="false">

View File

@ -1,4 +1,4 @@
name: Plex-HTTPD name: Module-HTTPD
version: 1.0.2 version: 1.4-SNAPSHOT
description: HTTPD server for Plex description: HTTPD server for Plex
main: dev.plex.HTTPDModule main: dev.plex.HTTPDModule