diff --git a/Plugin.cs b/Plugin.cs index 01b5574..2804982 100644 --- a/Plugin.cs +++ b/Plugin.cs @@ -1,5 +1,7 @@ -using System.Reflection; +using System.Collections.Generic; +using System.Reflection; using BepInEx; +using BepInEx.Configuration; using BepInEx.Logging; using BepInEx.Unity.IL2CPP; using Bloodstone.API; @@ -15,10 +17,27 @@ namespace v_rising_discord_bot_companion; [Reloadable] public class Plugin : BasePlugin { - public static ManualLogSource Logger = null!; + public static ManualLogSource Logger { get; private set; } = null!; + public static Plugin Instance { get; private set; } = null!; private Harmony? _harmony; private Component? _queryDispatcher; + private PluginConfig? _pluginConfig; + private ConfigEntry _basicAuthUsers; + + public Plugin() { + + Instance = this; + Logger = Log; + + _basicAuthUsers = Config.Bind( + "Authentication", + "BasicAuthUsers", + "", + "A list of comma separated username:password entries that are allowed to query the HTTP API." + ); + } + public override void Load() { if (!VWorld.IsServer) { @@ -26,7 +45,6 @@ public override void Load() { return; } - Logger = Log; // Plugin startup logic Log.LogInfo($"Plugin {MyPluginInfo.PLUGIN_GUID} version {MyPluginInfo.PLUGIN_VERSION} is loaded!"); @@ -45,6 +63,32 @@ public override bool Unload() { if (_queryDispatcher != null) { Object.Destroy(_queryDispatcher); } + _pluginConfig = null; return true; } + + public PluginConfig GetPluginConfig() { + _pluginConfig ??= ParsePluginConfig(); + return (PluginConfig) _pluginConfig; + } + + private PluginConfig ParsePluginConfig() { + + var basicAuthUsers = new List(); + foreach (var basicAuthUser in _basicAuthUsers.Value.Split(",")) { + var parts = basicAuthUser.Split(":"); + if (parts.Length == 2) { + basicAuthUsers.Add( + new BasicAuthUser( + Username: parts[0].Trim(), + Password: parts[1].Trim() + ) + ); + } + } + + return new PluginConfig( + BasicAuthUsers: basicAuthUsers + ); + } } diff --git a/PluginConfig.cs b/PluginConfig.cs new file mode 100644 index 0000000..cf9daa7 --- /dev/null +++ b/PluginConfig.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; + +namespace v_rising_discord_bot_companion; + +public readonly record struct PluginConfig( + List BasicAuthUsers +); + +public readonly record struct BasicAuthUser( + string Username, + string Password +); diff --git a/command/HttpReceiveServicePatches.cs b/command/HttpReceiveServicePatches.cs new file mode 100644 index 0000000..d008041 --- /dev/null +++ b/command/HttpReceiveServicePatches.cs @@ -0,0 +1,64 @@ +using System.Linq; +using HarmonyLib; +using Il2CppSystem.Net; +using Il2CppSystem.Security.Principal; +using ProjectM.Network; + +namespace v_rising_discord_bot_companion.command; + +[HarmonyPatch(typeof(HttpServiceReceiveThread))] +public class HttpReceiveServicePatches { + + [HarmonyPrefix] + [HarmonyPatch("IsAllowed")] + public static bool IsAllowed(HttpListenerContext context, ref bool __result) { + + var pluginConfig = Plugin.Instance.GetPluginConfig(); + + if (pluginConfig.BasicAuthUsers.Count <= 0) { + return true; + } + + var currentBasicAuthUser = ParseBasicAuthUser(context); + if (!currentBasicAuthUser.HasValue) { + __result = false; + return false; + } + + __result = IsAuthorized((BasicAuthUser) currentBasicAuthUser); + return __result; + } + + private static BasicAuthUser? ParseBasicAuthUser(HttpListenerContext context) { + + context.ParseAuthentication(AuthenticationSchemes.Basic); + + if (context.user == null) { + return null; + } + + var principal = context.user.TryCast(); + var identity = principal?.m_identity.TryCast(); + + if (identity == null) { + return null; + } + + var username = identity.Name; + var password = identity.password; + + if (username == null || password == null) { + return null; + } + + return new BasicAuthUser( + Username: username, + Password: password + ); + } + + private static bool IsAuthorized(BasicAuthUser currentBasicAuthUser) { + return Plugin.Instance.GetPluginConfig().BasicAuthUsers + .Count(it => it.Username.Equals(currentBasicAuthUser.Username) && it.Password.Equals(currentBasicAuthUser.Password)) == 1; + } +} diff --git a/command/ServerWebAPISystemPatches.cs b/command/ServerWebAPISystemPatches.cs index 9a47328..f4c934d 100644 --- a/command/ServerWebAPISystemPatches.cs +++ b/command/ServerWebAPISystemPatches.cs @@ -30,6 +30,7 @@ public class ServerWebAPISystemPatches { public static void OnCreate(ServerWebAPISystem __instance) { if (!SettingsManager.ServerHostSettings.API.Enabled) { + Plugin.Logger.LogInfo($"HTTP API is not enabled."); return; } @@ -50,6 +51,8 @@ public static void OnCreate(ServerWebAPISystem __instance) { "GET", BuildAdapter(_ => VampireDownedServerEventSystemPatches.getPvpKills()) )); + + Plugin.Logger.LogInfo($"Added v-rising-discord-bot endpoints."); } private static HttpServiceReceiveThread.RequestHandler BuildAdapter(Func commandHandler) { diff --git a/http-requests.http b/http-requests.http index cc31805..1a0fe82 100644 --- a/http-requests.http +++ b/http-requests.http @@ -6,3 +6,31 @@ GET http://localhost:25570/v-rising-discord-bot/player-activities ### Get pvp kills GET http://localhost:25570/v-rising-discord-bot/pvp-kills + +### Metrics +GET http://localhost:25570/metrics + +### Console +GET http://localhost:25570/console/v1?input=sendserverannouncement some message + +### Messages +POST http://localhost:25570/api/message/v1 +Content-Type: application/json + +{ + "message": "the actual message" +} + +### Shutdown +POST http://localhost:25570/api/shutdown/v1 +Content-Type: application/json + +{ + "shutdown": true +} + +### Save +POST http://localhost:25570/api/save/v1 +Content-Type: application/json + +{}