Change /pass to operate as an event callback rather than as a command

This avoids the issue where the server is configured so that password verification is required, but players do not have sufficient permission to use the /pass command
This commit is contained in:
UnknownShadow200 2023-11-18 16:35:10 +11:00
parent f9a32d7381
commit 8c9f0e60ea
11 changed files with 209 additions and 160 deletions

View File

@ -16,6 +16,7 @@ using System;
using System.Collections.Generic;
using MCGalaxy.Blocks;
using MCGalaxy.Commands.CPE;
using MCGalaxy.Events.PlayerEvents;
using BlockID = System.UInt16;
namespace MCGalaxy.Commands.Info
@ -31,6 +32,10 @@ namespace MCGalaxy.Commands.Info
}
public override void Use(Player p, string message, CommandData data) {
bool cancel = false;
OnPlayerHelpEvent.Call(p, message, ref cancel);
if (cancel) return;
if (message.Length == 0) {
PrintHelpMenu(p);
} else if (message.CaselessEq("ranks")) {

View File

@ -1,115 +0,0 @@
/*
Written by Jack1312
Copyright 2011-2012 MCForge
Dual-licensed under the Educational Community License, Version 2.0 and
the GNU General Public License, Version 3 (the "Licenses"); you may
not use this file except in compliance with the Licenses. You may
obtain a copy of the Licenses at
https://opensource.org/license/ecl-2-0/
https://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing,
software distributed under the Licenses are distributed on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using System;
using MCGalaxy.Authentication;
namespace MCGalaxy.Commands.Moderation {
public sealed class CmdPass : Command2 {
public override string name { get { return "Pass"; } }
public override string type { get { return CommandTypes.Moderation; } }
public override LevelPermission defaultRank { get { return LevelPermission.Operator; } }
public override bool LogUsage { get { return false; } }
public override bool UpdatesLastCmd { get { return false; } }
public override CommandPerm[] ExtraPerms {
get { return new[] { new CommandPerm(LevelPermission.Owner, "can reset passwords") }; }
}
public override CommandAlias[] Aliases {
get { return new[] { new CommandAlias("SetPass", "set"), new CommandAlias("ResetPass", "reset") }; }
}
public override void Use(Player p, string message, CommandData data) {
if (data.Rank < Server.Config.VerifyAdminsRank) {
Formatter.MessageNeedMinPerm(p, "+ can verify or set a password", Server.Config.VerifyAdminsRank); return;
}
if (!Server.Config.verifyadmins) { p.Message("Password verification is not currently enabled."); return; }
if (message.Length == 0) { Help(p); return; }
string[] args = message.SplitSpaces(2);
if (args.Length == 2 && args[0].CaselessEq("set")) {
SetPassword(p, args[1]);
} else if (args.Length == 2 && args[0].CaselessEq("reset")) {
ResetPassword(p, args[1], data);
} else {
VerifyPassword(p, message);
}
}
static void VerifyPassword(Player p, string password) {
if (!p.Unverified) { p.Message("&WYou are already verified."); return; }
if (p.passtries >= 3) { p.Kick("Did you really think you could keep on guessing?"); return; }
if (password.IndexOf(' ') >= 0) { p.Message("Your password must be &Wone &Sword!"); return; }
if (!PassAuthenticator.Current.HasPassword(p.name)) {
p.Message("You have not &Wset a password, &Suse &T/SetPass [Password] &Wto set one!");
return;
}
if (PassAuthenticator.VerifyPassword(p, password)) return;
p.passtries++;
p.Message("&WWrong Password. &SRemember your password is &Wcase sensitive.");
p.Message("Forgot your password? Contact &W{0} &Sto &Wreset it.", Server.Config.OwnerName);
}
static void SetPassword(Player p, string password) {
if (p.Unverified && PassAuthenticator.Current.HasPassword(p.name)) {
PassAuthenticator.Current.RequiresVerification(p, "can change your password");
p.Message("Forgot your password? Contact &W{0} &Sto &Wreset it.", Server.Config.OwnerName);
return;
}
if (password.IndexOf(' ') >= 0) { p.Message("&WPassword must be one word."); return; }
PassAuthenticator.Current.StorePassword(p.name, password);
p.Message("Your password was &aset to: &c" + password);
}
void ResetPassword(Player p, string name, CommandData data) {
string target = PlayerInfo.FindMatchesPreferOnline(p, name);
if (target == null) return;
if (p.Unverified) {
PassAuthenticator.Current.RequiresVerification(p, "can reset passwords");
return;
}
if (!CheckResetPerms(p, data)) return;
if (PassAuthenticator.Current.ResetPassword(target)) {
p.Message("Reset password for {0}", p.FormatNick(target));
} else {
p.Message("{0} &Sdoes not have a password.", p.FormatNick(target));
}
}
bool CheckResetPerms(Player p, CommandData data) {
// check server owner name for permissions backwards compatibility
return Server.Config.OwnerName.CaselessEq(p.name) || CheckExtraPerm(p, data, 1);
}
public override void Help(Player p) {
p.Message("&T/Pass reset [player] &H- Resets the password for that player");
p.Message("&T/Pass set [password] &H- Sets your password to [password]");
p.Message("&H Note: &WDo NOT set this as your Minecraft password!");
p.Message("&T/Pass [password]");
p.Message("&HIf you are an admin, use this command to verify your login.");
p.Message("&H You must be verified to use commands, modify blocks, and chat");
}
}
}

View File

@ -46,11 +46,7 @@ namespace MCGalaxy {
/* rotation origin point */
public Vec3F32 rotationOrigin;
public Vec3F32 rotation = new Vec3F32 {
X = 0.0f,
Y = 0.0f,
Z = 0.0f,
};
public Vec3F32 rotation;
public CustomModelAnim[] anims;
public bool fullbright = false;
public bool firstPersonArm = false;

View File

@ -72,6 +72,21 @@ namespace MCGalaxy.Events.PlayerEvents
}
}
public delegate void OnPlayerHelp(Player p, string target, ref bool cancel);
/// <summary> Called whenever a player attempts to display help for something via /help </summary>
/// <remarks> You can cancel this event to prevent the default /help behaviour. </remarks>
public sealed class OnPlayerHelpEvent : IEvent<OnPlayerHelp>
{
public static void Call(Player p, string target, ref bool cancel) {
IEvent<OnPlayerHelp>[] items = handlers.Items;
for (int i = 0; i < items.Length; i++)
{
try { items[i].method(p, target, ref cancel); }
catch (Exception ex) { LogHandlerException(ex, items[i]); }
}
}
}
public delegate void OnPlayerConnect(Player p);
/// <summary> Called whenever a player connects to the server </summary>
public sealed class OnPlayerConnectEvent: IEvent<OnPlayerConnect>

View File

@ -261,7 +261,6 @@
<Compile Include="Commands\Moderation\CmdMute.cs" />
<Compile Include="Commands\Moderation\CmdOhide.cs" />
<Compile Include="Commands\Moderation\CmdP2P.cs" />
<Compile Include="Commands\Moderation\CmdPass.cs" />
<Compile Include="Commands\Moderation\CmdPatrol.cs" />
<Compile Include="Commands\Moderation\CmdPossess.cs" />
<Compile Include="Commands\Moderation\CmdReport.cs" />

View File

@ -65,7 +65,7 @@ namespace MCGalaxy
bool deletingBlock = !painting && !placing;
if (Unverified) {
PassAuthenticator.Current.RequiresVerification(this, "modify blocks");
ExtraAuthenticator.Current.RequiresVerification(this, "modify blocks");
RevertBlock(x, y, z); return;
}
@ -585,7 +585,7 @@ namespace MCGalaxy
Message("You cannot use any commands while jailed."); return false;
}
if (Unverified && !(cmd == "pass" || cmd == "setpass")) {
PassAuthenticator.Current.RequiresVerification(this, "use /" + cmd);
ExtraAuthenticator.Current.RequiresVerification(this, "use /" + cmd);
return false;
}

View File

@ -48,8 +48,8 @@ namespace MCGalaxy
if (cancelconnecting) { cancelconnecting = false; return false; }
// mppass can be used as /pass when it is not used for name authentication
if (!verifiedName && NeedsVerification() && PassAuthenticator.Current.HasPassword(name))
PassAuthenticator.VerifyPassword(this, mppass);
if (!verifiedName && NeedsVerification())
ExtraAuthenticator.Current.AutoVerify(this, mppass);
level = Server.mainLevel;
Loading = true;

View File

@ -347,7 +347,7 @@ namespace MCGalaxy {
Message("Cannot {0} &Swhile chat moderation is on without &T/Voice&S", action); return false;
}
if (Unverified) {
PassAuthenticator.Current.RequiresVerification(this, action);
ExtraAuthenticator.Current.RequiresVerification(this, action);
return false;
}
return true;
@ -362,7 +362,7 @@ namespace MCGalaxy {
/// <summary> Checks if player is currently unverified, and if so, sends a message informing them </summary>
public void CheckIsUnverified() {
if (NeedsVerification()) PassAuthenticator.Current.NeedVerification(this);
if (NeedsVerification()) ExtraAuthenticator.Current.NeedVerification(this);
}

View File

@ -1,42 +1,74 @@
/*
Copyright 2015 MCGalaxy
Written by Jack1312
Copyright 2011-2012 MCForge
Dual-licensed under the Educational Community License, Version 2.0 and
the GNU General Public License, Version 3 (the "Licenses"); you may
not use this file except in compliance with the Licenses. You may
obtain a copy of the Licenses at
https://opensource.org/license/ecl-2-0/
https://www.gnu.org/licenses/gpl-3.0.html
https://opensource.org/license/ecl-2-0/
https://www.gnu.org/licenses/gpl-3.0.html
Unless required by applicable law or agreed to in writing,
software distributed under the Licenses are distributed on an "AS IS"
BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
or implied. See the Licenses for the specific language governing
permissions and limitations under the Licenses.
*/
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using MCGalaxy.Events.PlayerEvents;
namespace MCGalaxy.Authentication
{
/// <summary> Manages optional additional verification for certain users </summary>
public abstract class PassAuthenticator
{
public abstract class ExtraAuthenticator
{
/// <summary> The currently/actively used authenticator </summary>
public static PassAuthenticator Current = new DefaultPassAuthenticator();
public static ExtraAuthenticator Current { get; protected set; }
protected abstract void Activate();
protected abstract void Deactivate();
public static void SetActive(ExtraAuthenticator auth) {
if (Current != null) Current.Deactivate();
Current = auth;
auth.Activate();
}
/// <summary> Informs the given player that they must first
/// verify before they can perform the given action </summary>
public virtual void RequiresVerification(Player p, string action) {
public abstract void RequiresVerification(Player p, string action);
/// <summary> Informs the given player that they should verify,
/// otherwise they will be unable to perform some actions </summary>
public abstract void NeedVerification(Player p);
/// <summary> Attempts to automatically verify the player at login </summary>
public abstract void AutoVerify(Player p, string mppass);
protected void Verify(Player p) {
p.Message("You are now &averified &Sand can now &ause commands, modify blocks, and chat.");
p.verifiedPass = true;
p.Unverified = false;
}
}
/// <summary> Performs extra authentication using a per player password </summary>
public abstract class PassAuthenticator : ExtraAuthenticator
{
public override void RequiresVerification(Player p, string action) {
p.Message("&WYou must first verify with &T/Pass [Password] &Wbefore you can {0}", action);
}
/// <summary> Informs the given player that they should verify,
/// otherwise they will be unable to perform some actions </summary>
public virtual void NeedVerification(Player p) {
public override void NeedVerification(Player p) {
if (!HasPassword(p.name)) {
p.Message("&WPlease set your account verification password with &T/SetPass [password]!");
} else {
@ -44,10 +76,21 @@ namespace MCGalaxy.Authentication
}
}
public override void AutoVerify(Player p, string mppass) {
if (!HasPassword(p.name)) return;
if (!VerifyPassword(p.name, mppass)) return;
Verify(p);
}
/// <summary> Returns whether the given player has a stored password </summary>
public abstract bool HasPassword(string name);
/// <summary> Returns whether the given pasword equals
/// the stored password for the given player </summary>
public abstract bool VerifyPassword(string name, string password);
/// <summary> Sets the stored password for the given player </summary>
public abstract void StorePassword(string name, string password);
@ -55,38 +98,133 @@ namespace MCGalaxy.Authentication
/// <returns> Whether the given player actually had a stored password </returns>
public abstract bool ResetPassword(string name);
/// <summary> Returns whether the given pasword equals
/// the stored password for the given player </summary>
public abstract bool VerifyPassword(string name, string password);
protected override void Activate() {
OnPlayerHelpEvent.Register(OnPlayerHelp, Priority.Low);
OnPlayerCommandEvent.Register(OnPlayerCommand, Priority.Low);
}
public static bool VerifyPassword(Player p, string password) {
if (!Current.VerifyPassword(p.name, password))
return false;
protected override void Deactivate() {
OnPlayerHelpEvent.Unregister(OnPlayerHelp);
OnPlayerCommandEvent.Unregister(OnPlayerCommand);
}
void OnPlayerHelp(Player p, string target, ref bool cancel) {
if (!(target.CaselessEq("pass") || target.CaselessEq("password") || target.CaselessEq("setpass"))) return;
p.Message("You are now &averified &Sand can now &ause commands, modify blocks, and chat.");
p.verifiedPass = true;
p.Unverified = false;
return true;
PrintHelp(p);
cancel = true;
}
void OnPlayerCommand(Player p, string cmd, string args, CommandData data) {
if (cmd.CaselessEq("pass")) {
ExecPassCommand(p, args, data);
p.cancelcommand = true;
} else if (cmd.CaselessEq("setpass")) {
ExecPassCommand(p, "set " + args, data);
p.cancelcommand = true;
} else if (cmd.CaselessEq("resetpass")) {
ExecPassCommand(p, "reset " + args, data);
p.cancelcommand = true;
}
}
void ExecPassCommand(Player p, string message, CommandData data) {
if (!Server.Config.verifyadmins) {
p.Message("Password verification is not currently enabled."); return;
}
if (data.Rank < Server.Config.VerifyAdminsRank) {
Formatter.MessageNeedMinPerm(p, "+ require password verification",
Server.Config.VerifyAdminsRank); return;
}
message = message.Trim();
if (message.Length == 0) { PrintHelp(p); return; }
string[] args = message.SplitSpaces(2);
if (args.Length == 2 && args[0].CaselessEq("set")) {
DoSetPassword(p, args[1]);
} else if (args.Length == 2 && args[0].CaselessEq("reset")) {
DoResetPassword(p, args[1], data);
} else {
DoVerifyPassword(p, message);
}
}
void DoVerifyPassword(Player p, string password) {
if (!p.Unverified) { p.Message("&WYou are already verified."); return; }
if (p.passtries >= 3) { p.Kick("Did you really think you could keep on guessing?"); return; }
if (password.IndexOf(' ') >= 0) { p.Message("Your password must be &Wone &Sword!"); return; }
if (!HasPassword(p.name)) {
p.Message("You have not &Wset a password, &Suse &T/SetPass [Password] &Wto set one");
p.Message("Make sure to use a different password than your Minecraft one!");
return;
}
if (VerifyPassword(p.name, password)) {
Verify(p); return;
}
p.passtries++;
p.Message("&WWrong Password. &SRemember your password is &Wcase sensitive.");
p.Message("Forgot your password? Contact &W{0} &Sto &Wreset it.", Server.Config.OwnerName);
}
void DoSetPassword(Player p, string password) {
if (p.Unverified && HasPassword(p.name)) {
RequiresVerification(p, "can change your password");
p.Message("Forgot your password? Contact &W{0} &Sto &Wreset it.", Server.Config.OwnerName);
return;
}
if (password.IndexOf(' ') >= 0) { p.Message("&WPassword must be one word."); return; }
StorePassword(p.name, password);
p.Message("Your password was &aset to: &c" + password);
}
void DoResetPassword(Player p, string name, CommandData data) {
string target = PlayerInfo.FindMatchesPreferOnline(p, name);
if (target == null) return;
if (p.Unverified) {
RequiresVerification(p, "can reset passwords");
return;
}
if (data.Rank < Server.Config.ResetPasswordRank) {
p.Message("Only {0}&S+ can reset passwords",
Group.GetColoredName(Server.Config.ResetPasswordRank));
return;
}
if (ResetPassword(target)) {
p.Message("Reset password for {0}", p.FormatNick(target));
} else {
p.Message("{0} &Sdoes not have a password.", p.FormatNick(target));
}
}
static void PrintHelp(Player p) {
p.Message("&T/Pass reset [player] &H- Resets the password for that player");
p.Message("&H Note that only {0}&S+ can reset passwords",
Group.GetColoredName(Server.Config.ResetPasswordRank));
p.Message("&T/Pass set [password] &H- Sets your password to [password]");
p.Message("&H Note: &WDo NOT set this as your Minecraft password!");
p.Message("&T/Pass [password]");
p.Message("&H If you are {0}&H+, use this command to verify your login.",
Group.GetColoredName(Server.Config.VerifyAdminsRank));
p.Message("&H You must be verified to use commands, modify blocks, and chat");
}
}
/// <summary> Password authenticator that loads/stores passwords in /extra/passwords folder </summary>
public class DefaultPassAuthenticator : PassAuthenticator
public class DefaultPassAuthenticator : PassAuthenticator
{
const string PASS_FOLDER = "extra/passwords/";
public override bool HasPassword(string name) { return GetHashPath(name) != null; }
public override bool ResetPassword(string name) {
string path = GetHashPath(name);
if (path == null) return false;
File.Delete(path);
return true;
}
public override bool VerifyPassword(string name, string password) {
public override bool VerifyPassword(string name, string password) {
string path = GetHashPath(name);
if (path == null) return false;
@ -100,6 +238,14 @@ namespace MCGalaxy.Authentication
File.WriteAllBytes(HashPath(name), hash);
}
public override bool ResetPassword(string name) {
string path = GetHashPath(name);
if (path == null) return false;
File.Delete(path);
return true;
}
static string GetHashPath(string name) {
string path = HashPath(name);
@ -130,7 +276,7 @@ namespace MCGalaxy.Authentication
static bool ArraysEqual(byte[] a, byte[] b) {
if (a.Length != b.Length) return false;
for (int i = 0; i < a.Length; i++)
for (int i = 0; i < a.Length; i++)
{
if (a[i] != b[i]) return false;
}

View File

@ -90,6 +90,7 @@ namespace MCGalaxy
Logger.Log(LogType.SystemActivity, "Starting Server");
ServicePointManager.Expect100Continue = false;
ForceEnableTLS();
ExtraAuthenticator.SetActive(new DefaultPassAuthenticator());
SQLiteBackend.Instance.LoadDependencies();
#if !MCG_STANDALONE

View File

@ -62,6 +62,8 @@ namespace MCGalaxy
public bool verifyadmins = true;
[ConfigPerm("verify-admin-perm", "Security", LevelPermission.Operator)]
public LevelPermission VerifyAdminsRank = LevelPermission.Operator;
[ConfigPerm("reset-password-perm", "Security", LevelPermission.Operator)]
public LevelPermission ResetPasswordRank = LevelPermission.Owner;
[ConfigBool("support-web-client", "Webclient", true)]
public bool WebClient = true;