Ban rewrite

This commit is contained in:
Chris 2020-11-15 11:05:04 +10:30
parent 5af1f8f76a
commit 56de9f6684
9 changed files with 679 additions and 860 deletions

View file

@ -1267,289 +1267,215 @@ namespace TShockAPI
private static void Ban(CommandArgs args)
{
string subcmd = args.Parameters.Count == 0 ? "help" : args.Parameters[0].ToLower();
switch (subcmd)
//Ban syntax:
// ban add <target> [reason] [duration] [flags (default: -a -u -ip)]
// Valid flags: -a (ban account name), -u (ban UUID), -n (ban character name), -ip (ban IP address), -e (exact, ban the identifier provided as 'target')
// Unless -e is passed to the command, <target> is assumed to be a player or player index.
// ban del <target>
// Target is expected to be an identifier in the format 'identifier_prefix:identifier'. Eg acc:MyAccountName
// ban list [page]
// Displays a paginated list of bans
// ban details <target>
// Target is expected to be an identifier in the format 'identifier_prefix:identifier'. Eg acc:MyAccountName
// Output: Banned Identifier - expiration
// Reason: text
// Banned by: name
void Help()
{
case "add":
#region Add Ban
if (args.Parameters.Count > 1)
{
if (args.Parameters.Count < 2)
{
args.Player.SendErrorMessage("Invalid command. Format: {0}ban add <player> [time] [reason]", Specifier);
args.Player.SendErrorMessage("Example: {0}ban add Shank 10d Hacking and cheating", Specifier);
args.Player.SendErrorMessage("Example: {0}ban add Ash", Specifier);
args.Player.SendErrorMessage("Use the time 0 (zero) for a permanent ban.");
MoreHelp(args.Parameters[1].ToLower());
return;
}
// Used only to notify if a ban was successful and who the ban was about
bool success = false;
string targetGeneralizedName = "";
args.Player.SendMessage("TShock Ban Help", Color.White);
args.Player.SendMessage("Available Ban commands:", Color.White);
args.Player.SendMessage("ban [c/FFAAAA:add] <target> [flags]", Color.White);
args.Player.SendMessage("ban [c/FFAAAA:del] <target>", Color.White);
args.Player.SendMessage("ban [c/FFAAAA:list]", Color.White);
args.Player.SendMessage("ban [c/FFAAAA:details] <target>", Color.White);
args.Player.SendMessage("For more info, use [c/AAAAFF:ban help] [c/FFAAAA:command]", Color.White);
}
// Effective ban target assignment
List<TSPlayer> players = TSPlayer.FindByNameOrID(args.Parameters[1]);
void MoreHelp(string cmd)
{
switch (cmd)
{
case "add":
args.Player.SendMessage("", Color.White);
args.Player.SendMessage("Ban Add Syntax", Color.White);
args.Player.SendMessage("[c/AAAAFF:ban add] [c/FFAAAA:<target>] [[c/AAAAFF:reason]] [[c/FFAAFF:duration]] [[c/AAFFAA:flags]]", Color.White);
args.Player.SendMessage("- [c/FFAAFF:Duration]: uses the format [c/FFAAFF:0d0m0s] to determine the length of the ban. Eg a value of [c/FFAAFF:10d30m0s] would represent 10 days, 30 minutes, 0 seconds.", Color.White);
args.Player.SendMessage("- [c/AAFFAA:flags]: -a (account name), -u (UUID), -n (character name), -ip (IP address), -e (exact, [c/FFAAAA:target] will be treated as identifier)", Color.White);
args.Player.SendMessage(" Unless [c/AAFFAA:-e] is passed to the command, [c/FFAAAA:target] is assumed to be a player or player index", Color.White);
args.Player.SendMessage(" If no [c/AAFFAA:flags] are specified, the command uses [c/AAFFAA:-a -u -ip] by default.", Color.White);
args.Player.SendMessage("Example usage: [c/AAAAFF:ban add] [c/FFAAAA:ExamplePlayer] [c/AAAAFF:\"Cheating\"] 10d30m0s [c/AAFFAA:-a -u -ip]", Color.White);
break;
case "del":
args.Player.SendMessage("", Color.White);
args.Player.SendMessage("Ban Del Syntax", Color.White);
args.Player.SendMessage("[c/AAAAFF:ban del] [c/FFAAAA:target]", Color.White);
args.Player.SendMessage("- [c/FFAAAA:Target] is expected to be an identifier in the format 'identifier_prefix:identifier'. Eg [c/FFAAAA:acc:MyAccountName]", Color.White);
args.Player.SendMessage("Example usage: [c/AAAAFF:ban del] [c/FFAAAA:acc:ExampleAccount]", Color.White);
break;
case "list":
args.Player.SendMessage("", Color.White);
args.Player.SendMessage("Ban List Syntax", Color.White);
args.Player.SendMessage("[c/AAAAFF:ban list] [[c/FFAAFF:page]]", Color.White);
args.Player.SendMessage("- Lists active bans. Color trends towards green as the ban approaches expiration", Color.White);
args.Player.SendMessage("Example usage: [c/AAAAFF:ban list]", Color.White);
break;
case "details":
args.Player.SendMessage("", Color.White);
args.Player.SendMessage("Ban Details Syntax", Color.White);
args.Player.SendMessage("[c/AAAAFF:ban details] [c/FFAAAA:target]", Color.White);
args.Player.SendMessage("- [c/FFAAAA:Target] is expected to be an identifier in the format 'identifier_prefix:identifier'. Eg [c/FFAAAA:acc:MyAccountName]", Color.White);
args.Player.SendMessage("Example usage: [c/AAAAFF:ban details] [c/FFAAAA:acc:ExampleAccount]", Color.White);
break;
default:
args.Player.SendMessage("Unknown ban command. Try 'add', 'del', 'list', or 'details'", Color.White);
break;
}
}
void AddBan()
{
if (!args.Parameters.TryGetValue(1, out string target))
{
args.Player.SendMessage("Invalid Ban Add syntax. Refer to [c/AAAAFF:ban help add] for details on how to use the [c/AAAAFF:ban add] command", Color.White);
return;
}
bool exactTarget = args.Parameters.Any(p => p == "-e");
bool banAccount = args.Parameters.Any(p => p == "-a");
bool banUuid = args.Parameters.Any(p => p == "-u");
bool banName = args.Parameters.Any(p => p == "-n");
bool banIp = args.Parameters.Any(p => p == "-ip");
args.Parameters.TryGetValue(2, out string reason);
args.Parameters.TryGetValue(3, out string duration);
DateTime expiration = DateTime.MaxValue;
if (TShock.Utils.TryParseTime(duration, out int seconds))
{
expiration = DateTime.UtcNow.AddSeconds(seconds);
}
//If no flags were specified, default to account, uuid, and IP
if (!exactTarget && !banAccount && !banUuid && !banName && !banIp)
{
banAccount = banUuid = banIp = true;
}
if (exactTarget)
{
if (TShock.Bans.InsertBan(target, reason ?? "Banned", args.Player.Account.Name, DateTime.UtcNow, expiration) != null)
{
args.Player.SendSuccessMessage("Ban added.");
}
else
{
args.Player.SendErrorMessage("Failed to insert ban. Ban may already exist, or an error occured.");
}
return;
}
var players = TSPlayer.FindByNameOrID(target);
// Bad case: Players contains more than 1 person so we can't ban them
if (players.Count > 1)
{
//Fail fast
args.Player.SendMultipleMatchError(players.Select(p => p.Name));
return;
}
UserAccount offlineUserAccount = TShock.UserAccounts.GetUserAccountByName(args.Parameters[1]);
// Storage variable to determine if the command executor is the server console
// If it is, we assume they have full control and let them override permission checks
bool callerIsServerConsole = args.Player is TSServerPlayer;
// The ban reason the ban is going to have
string banReason = "Unknown.";
// The default ban length
// 0 is permanent ban, otherwise temp ban
int banLengthInSeconds = 0;
// Figure out if param 2 is a time or 0 or garbage
if (args.Parameters.Count >= 3)
if (players.Count < 1)
{
bool parsedOkay = false;
if (args.Parameters[2] != "0")
args.Player.SendErrorMessage("Could not find the target specified. Check that you have the correct spelling.");
return;
}
var player = players[0];
var identifiers = new List<string>();
string identifier;
if (banAccount)
{
parsedOkay = TShock.Utils.TryParseTime(args.Parameters[2], out banLengthInSeconds);
if (player.Account != null)
{
identifier = $"{DB.Ban.Identifiers.Account}{player.Account.Name}";
if (TShock.Bans.InsertBan(identifier, reason, args.Player.Account.Name, DateTime.UtcNow, expiration) != null)
{
identifiers.Add(identifier);
}
}
}
if (banUuid)
{
identifier = $"{DB.Ban.Identifiers.UUID}{player.UUID}";
if (TShock.Bans.InsertBan($"{DB.Ban.Identifiers.UUID}{player.UUID}", reason, args.Player.Account.Name, DateTime.UtcNow, expiration) != null)
{
identifiers.Add(identifier);
}
}
if (banName)
{
identifier = $"{DB.Ban.Identifiers.Name}{player.Name}";
if (TShock.Bans.InsertBan($"{DB.Ban.Identifiers.Name}{player.Name}", reason, args.Player.Account.Name, DateTime.UtcNow, expiration) != null)
{
identifiers.Add(identifier);
}
}
if (banIp)
{
identifier = $"{DB.Ban.Identifiers.IP}{player.IP}";
if (TShock.Bans.InsertBan($"{DB.Ban.Identifiers.IP}{player.IP}", reason, args.Player.Account.Name, DateTime.UtcNow, expiration) != null)
{
identifiers.Add(identifier);
}
}
args.Player.SendSuccessMessage("Bans added for identifiers: ", string.Join(", ", identifiers));
}
void DelBan()
{
if (!args.Parameters.TryGetValue(1, out string target))
{
args.Player.SendMessage("Invalid Ban Del syntax. Refer to [c/AAAAFF:ban help del] for details on how to use the [c/AAAAFF:ban del] command", Color.White);
return;
}
if (TShock.Bans.RemoveBan(target))
{
args.Player.SendSuccessMessage("Ban removed.");
}
else
{
parsedOkay = true;
}
if (!parsedOkay)
{
args.Player.SendErrorMessage("Invalid time format. Example: 10d 5h 3m 2s.");
args.Player.SendErrorMessage("Use 0 (zero) for a permanent ban.");
return;
args.Player.SendErrorMessage("Failed to remove ban.");
}
}
// If a reason exists, use the given reason.
if (args.Parameters.Count > 3)
{
banReason = String.Join(" ", args.Parameters.Skip(3));
}
// Good case: Online ban for matching character.
if (players.Count == 1)
{
TSPlayer target = players[0];
if (target.HasPermission(Permissions.immunetoban) && !callerIsServerConsole)
{
args.Player.SendErrorMessage("Permission denied. Target {0} is immune to ban.", target.Name);
return;
}
targetGeneralizedName = target.Name;
success = TShock.Bans.AddBan(target.IP, target.Name, target.UUID, target.Account?.Name ?? "", banReason, false, args.Player.Account.Name,
banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
// Since this is an online ban, we need to dc the player and tell them now.
if (success)
{
if (banLengthInSeconds == 0)
{
target.Disconnect(String.Format("Permanently banned for {0}", banReason));
}
else
{
target.Disconnect(String.Format("Banned for {0} seconds for {1}", banLengthInSeconds, banReason));
}
}
}
// Case: Players & user are invalid, could be IP?
// Note: Order matters. If this method is above the online player check,
// This enables you to ban an IP even if the player exists in the database as a player.
// You'll get two bans for the price of one, in theory, because both IP and user named IP will be banned.
// ??? edge cases are weird, but this is going to happen
// The only way around this is to either segregate off the IP code or do something else.
if (players.Count == 0)
{
// If the target is a valid IP...
string pattern = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";
Regex r = new Regex(pattern, RegexOptions.IgnoreCase);
if (r.IsMatch(args.Parameters[1]))
{
targetGeneralizedName = "IP: " + args.Parameters[1];
success = TShock.Bans.AddBan(args.Parameters[1], "", "", "", banReason,
false, args.Player.Account.Name, banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
if (success && offlineUserAccount != null)
{
args.Player.SendSuccessMessage("Target IP {0} was banned successfully.", targetGeneralizedName);
args.Player.SendErrorMessage("Note: An account named with this IP address also exists.");
args.Player.SendErrorMessage("Note: It will also be banned.");
}
}
else
{
// Apparently there is no way to not IP ban someone
// This means that where we would normally just ban a "character name" here
// We can't because it requires some IP as a primary key.
if (offlineUserAccount == null)
{
args.Player.SendErrorMessage("Unable to ban target {0}.", args.Parameters[1]);
args.Player.SendErrorMessage("Target is not a valid IP address, a valid online player, or a known offline user.");
return;
}
}
}
// Case: Offline ban
if (players.Count == 0 && offlineUserAccount != null)
{
// Catch: we don't know an offline player's last login character name
// This means that we're banning their *user name* on the assumption that
// user name == character name
// (which may not be true)
// This needs to be fixed in a future implementation.
targetGeneralizedName = offlineUserAccount.Name;
if (TShock.Groups.GetGroupByName(offlineUserAccount.Group).HasPermission(Permissions.immunetoban) &&
!callerIsServerConsole)
{
args.Player.SendErrorMessage("Permission denied. Target {0} is immune to ban.", targetGeneralizedName);
return;
}
if (offlineUserAccount.KnownIps == null)
{
args.Player.SendErrorMessage("Unable to ban target {0} because they have no valid IP to ban.", targetGeneralizedName);
return;
}
string lastIP = JsonConvert.DeserializeObject<List<string>>(offlineUserAccount.KnownIps).Last();
success =
TShock.Bans.AddBan(lastIP,
"", offlineUserAccount.UUID, offlineUserAccount.Name, banReason, false, args.Player.Account.Name,
banLengthInSeconds == 0 ? "" : DateTime.UtcNow.AddSeconds(banLengthInSeconds).ToString("s"));
}
if (success)
{
args.Player.SendSuccessMessage("{0} was successfully banned.", targetGeneralizedName);
args.Player.SendInfoMessage("Length: {0}", banLengthInSeconds == 0 ? "Permanent." : banLengthInSeconds + " seconds.");
args.Player.SendInfoMessage("Reason: {0}", banReason);
if (!args.Silent)
{
if (banLengthInSeconds == 0)
{
TSPlayer.All.SendErrorMessage("{0} was permanently banned by {1} for: {2}",
targetGeneralizedName, args.Player.Account.Name, banReason);
}
else
{
TSPlayer.All.SendErrorMessage("{0} was temp banned for {1} seconds by {2} for: {3}",
targetGeneralizedName, banLengthInSeconds, args.Player.Account.Name, banReason);
}
}
}
else
{
args.Player.SendErrorMessage("{0} was NOT banned due to a database error or other system problem.", targetGeneralizedName);
args.Player.SendErrorMessage("If this player is online, they have NOT been kicked.");
args.Player.SendErrorMessage("Check the system logs for details.");
}
return;
}
#endregion
case "del":
#region Delete ban
{
if (args.Parameters.Count != 2)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ban del <player>", Specifier);
return;
}
string plStr = args.Parameters[1];
Ban ban = TShock.Bans.GetBanByName(plStr, false);
if (ban != null)
{
if (TShock.Bans.RemoveBan(ban.Name, true))
args.Player.SendSuccessMessage("Unbanned {0} ({1}).", ban.Name, ban.IP);
else
args.Player.SendErrorMessage("Failed to unban {0} ({1}), check logs.", ban.Name, ban.IP);
}
else
args.Player.SendErrorMessage("No bans for {0} exist.", plStr);
}
#endregion
return;
case "delip":
#region Delete IP ban
{
if (args.Parameters.Count != 2)
{
args.Player.SendErrorMessage("Invalid syntax! Proper syntax: {0}ban delip <ip>", Specifier);
return;
}
string ip = args.Parameters[1];
Ban ban = TShock.Bans.GetBanByIp(ip);
if (ban != null)
{
if (TShock.Bans.RemoveBan(ban.IP, false))
args.Player.SendSuccessMessage("Unbanned IP {0} ({1}).", ban.IP, ban.Name);
else
args.Player.SendErrorMessage("Failed to unban IP {0} ({1}), check logs.", ban.IP, ban.Name);
}
else
args.Player.SendErrorMessage("IP {0} is not banned.", ip);
}
#endregion
return;
case "help":
#region Help
{
int pageNumber;
if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber))
return;
var lines = new List<string>
{
"add <target> <time> [reason] - Bans a player or user account if the player is not online.",
"del <player> - Unbans a player.",
"delip <ip> - Unbans an IP.",
"list [page] - Lists all player bans.",
"listip [page] - Lists all IP bans."
};
PaginationTools.SendPage(args.Player, pageNumber, lines,
new PaginationTools.Settings
{
HeaderFormat = "Ban Sub-Commands ({0}/{1}):",
FooterFormat = "Type {0}ban help {{0}} for more sub-commands.".SFormat(Specifier)
}
);
}
#endregion
return;
case "list":
#region List bans
void ListBans()
{
int pageNumber;
if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber))
{
args.Player.SendMessage("Invalid Ban List syntax. Refer to [c/AAAAFF:ban help list] for details on how to use the [c/AAAAFF:ban list] command", Color.White);
return;
}
List<Ban> bans = TShock.Bans.GetBans();
List<Ban> bans = TShock.Bans.GetAllBans();
var nameBans = from ban in bans
where !String.IsNullOrEmpty(ban.Name)
select ban.Name;
select ban.Identifier;
PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(nameBans),
new PaginationTools.Settings
@ -1559,36 +1485,53 @@ namespace TShockAPI
NothingToDisplayString = "There are currently no bans."
});
}
#endregion
return;
case "listip":
#region List IP bans
void BanDetails()
{
int pageNumber;
if (!PaginationTools.TryParsePageNumber(args.Parameters, 1, args.Player, out pageNumber))
if (!args.Parameters.TryGetValue(1, out string target))
{
args.Player.SendMessage("Invalid Ban Details syntax. Refer to [c/AAAAFF:ban help details] for details on how to use the [c/AAAAFF:ban details] command", Color.White);
return;
}
List<Ban> bans = TShock.Bans.GetBans();
Ban ban = TShock.Bans.GetBanByIdentifier(target);
var ipBans = from ban in bans
where String.IsNullOrEmpty(ban.Name)
select ban.IP;
PaginationTools.SendPage(args.Player, pageNumber, PaginationTools.BuildLinesFromTerms(ipBans),
new PaginationTools.Settings
if (ban == null)
{
HeaderFormat = "IP Bans ({0}/{1}):",
FooterFormat = "Type {0}ban listip {{0}} for more.".SFormat(Specifier),
NothingToDisplayString = "There are currently no IP bans."
});
}
#endregion
args.Player.SendErrorMessage("No ban found matching the given identifier");
return;
}
args.Player.SendMessage($"{ban.Identifier}", Color.White);
args.Player.SendMessage($"Reason: {ban.Reason}", Color.White);
args.Player.SendMessage($"Banned by: [c/AAFFAA:{ban.BanningUser}] at [c/AAAAFF:time]", Color.White);
}
string subcmd = args.Parameters.Count == 0 ? "help" : args.Parameters[0].ToLower();
switch (subcmd)
{
case "help":
Help();
break;
case "add":
AddBan();
break;
case "del":
DelBan();
break;
case "list":
ListBans();
break;
case "details":
BanDetails();
break;
default:
args.Player.SendErrorMessage("Invalid subcommand! Type {0}ban help for more information.", Specifier);
return;
break;
}
}

View file

@ -31,6 +31,15 @@ namespace TShockAPI.DB
{
private IDbConnection database;
/// <summary>
/// Event invoked when a ban check occurs
/// </summary>
public static event EventHandler<BanEventArgs> OnBanCheck;
/// <summary>
/// Event invoked when a ban is added
/// </summary>
public static event EventHandler<BanEventArgs> OnBanAdded;
/// <summary>
/// Initializes a new instance of the <see cref="TShockAPI.DB.BanManager"/> class.
/// </summary>
@ -39,15 +48,12 @@ namespace TShockAPI.DB
{
database = db;
var table = new SqlTable("Bans",
new SqlColumn("IP", MySqlDbType.String, 16) { Primary = true },
new SqlColumn("Name", MySqlDbType.Text),
new SqlColumn("UUID", MySqlDbType.Text),
var table = new SqlTable("PlayerBans",
new SqlColumn("Identifier", MySqlDbType.Text) { Primary = true, Unique = true },
new SqlColumn("Reason", MySqlDbType.Text),
new SqlColumn("BanningUser", MySqlDbType.Text),
new SqlColumn("Date", MySqlDbType.Text),
new SqlColumn("Expiration", MySqlDbType.Text),
new SqlColumn("AccountName", MySqlDbType.Text)
new SqlColumn("Expiration", MySqlDbType.Text)
);
var creator = new SqlTableCreator(db,
db.GetSqlType() == SqlType.Sqlite
@ -62,56 +68,205 @@ namespace TShockAPI.DB
System.Console.WriteLine("Possible problem with your database - is Sqlite3.dll present?");
throw new Exception("Could not find a database library (probably Sqlite3.dll)");
}
OnBanCheck += CheckBanValid;
}
/// <summary>
/// Gets a ban by IP.
/// Converts bans from the old ban system to the new.
/// </summary>
/// <param name="ip">The IP.</param>
/// <returns>The ban.</returns>
public Ban GetBanByIp(string ip)
public void ConvertBans()
{
try
using (var reader = database.QueryReader("SELECT * FROM Bans"))
{
using (var reader = database.QueryReader("SELECT * FROM Bans WHERE IP=@0", ip))
while (reader.Read())
{
var ip = reader.Get<string>("IP");
var uuid = reader.Get<string>("UUID");
var account = reader.Get<string>("AccountName");
var reason = reader.Get<string>("Reason");
var banningUser = reader.Get<string>("BanningUser");
var date = reader.Get<string>("Date");
var expiration = reader.Get<string>("Expiration");
if (!string.IsNullOrWhiteSpace(ip))
{
InsertBan($"{Ban.Identifiers.IP}{ip}", reason, banningUser, date, expiration);
}
if (!string.IsNullOrWhiteSpace(account))
{
InsertBan($"{Ban.Identifiers.Account}{account}", reason, banningUser, date, expiration);
}
if (!string.IsNullOrWhiteSpace(uuid))
{
InsertBan($"{Ban.Identifiers.UUID}{uuid}", reason, banningUser, date, expiration);
}
}
}
}
/// <summary>
/// Determines whether or not a ban is valid
/// </summary>
/// <param name="ban"></param>
/// <returns></returns>
public bool IsValidBan(Ban ban)
{
BanEventArgs args = new BanEventArgs { Ban = ban };
OnBanCheck?.Invoke(this, args);
return args.Valid;
}
internal void CheckBanValid(object sender, BanEventArgs args)
{
//We consider a ban to be valid if the start time is before now and the end time is after now
args.Valid = args.Ban.BanDateTime < DateTime.UtcNow && args.Ban.ExpirationDateTime > DateTime.UtcNow;
}
/// <summary>
/// Adds a new ban for the given identifier. If the addition succeeds, returns a ban object with the ban details. Else returns null
/// </summary>
/// <param name="identifier"></param>
/// <param name="reason"></param>
/// <param name="banningUser"></param>
/// <param name="fromDate"></param>
/// <param name="toDate"></param>
/// <returns></returns>
public Ban InsertBan(string identifier, string reason, string banningUser, DateTime fromDate, DateTime toDate)
=> InsertBan(identifier, reason, banningUser, fromDate.ToString("s"), toDate.ToString("s"));
/// <summary>
/// Adds a new ban for the given identifier. If the addition succeeds, returns a ban object with the ban details. Else returns null
/// </summary>
/// <param name="identifier"></param>
/// <param name="reason"></param>
/// <param name="banningUser"></param>
/// <param name="fromDate"></param>
/// <param name="toDate"></param>
/// <returns></returns>
public Ban InsertBan(string identifier, string reason, string banningUser, string fromDate, string toDate)
{
Ban b = new Ban(identifier, reason, banningUser, fromDate, toDate);
BanEventArgs args = new BanEventArgs { Ban = b };
OnBanAdded?.Invoke(this, args);
if (!args.Valid)
{
return null;
}
int rowsModified = database.Query("INSERT OR IGNORE INTO PlayerBans (Identifier, Reason, BanningUser, Date, Expiration) VALUES (@0, @1, @2, @3, @4);", identifier, reason, banningUser, fromDate, toDate);
//Return the ban if the query actually inserted the row. If the given identifier already exists, the INSERT is ignored and 0 rows are modified.
return rowsModified != 0 ? b : null;
}
/// <summary>
/// Attempts to remove a ban. Returns true if the ban was removed or expired. False if the ban could not be removed or expired
/// </summary>
/// <param name="identifier"></param>
/// <param name="fullDelete">If true, deletes the ban from the database. If false, marks the expiration time as now, rendering the ban expired. Defaults to false</param>
/// <returns></returns>
public bool RemoveBan(string identifier, bool fullDelete = false)
{
int rowsModified;
if (fullDelete)
{
rowsModified = database.Query("DELETE FROM PlayerBans WHERE Identifier=@0", identifier);
}
else
{
rowsModified = database.Query("UPDATE PlayerBans SET Expiration=@0 WHERE Identifier=@1", DateTime.UtcNow.ToString("s"), identifier);
}
return rowsModified > 0;
}
/// <summary>
/// Retrieves a ban for a given identifier, or null if no matches are found
/// </summary>
/// <param name="identifier"></param>
/// <returns></returns>
public Ban GetBanByIdentifier(string identifier)
{
using (var reader = database.QueryReader("SELECT * FROM PlayerBans WHERE Identifier=@0", identifier))
{
if (reader.Read())
return new Ban(reader.Get<string>("IP"), reader.Get<string>("Name"), reader.Get<string>("UUID"), reader.Get<string>("AccountName"), reader.Get<string>("Reason"), reader.Get<string>("BanningUser"), reader.Get<string>("Date"), reader.Get<string>("Expiration"));
}
}
catch (Exception ex)
{
TShock.Log.Error(ex.ToString());
var id = reader.Get<string>("Identifier");
var reason = reader.Get<string>("Reason");
var banningUser = reader.Get<string>("BanningUser");
var date = reader.Get<string>("Date");
var expiration = reader.Get<string>("Expiration");
return new Ban(id, reason, banningUser, date, expiration);
}
}
return null;
}
/// <summary>
/// Retrieves an enumerable of bans for a given set of identifiers
/// </summary>
/// <param name="identifiers"></param>
/// <returns></returns>
public IEnumerable<Ban> GetBansByIdentifiers(params string[] identifiers)
{
//Generate a sequence of '@0, @1, @2, ... etc'
var parameters = string.Join(", ", Enumerable.Range(0, identifiers.Count()).Select(p => $"@{p}"));
using (var reader = database.QueryReader($"SELECT * FROM PlayerBans WHERE Identifier IN ({parameters})", identifiers))
{
while (reader.Read())
{
var id = reader.Get<string>("Identifier");
var reason = reader.Get<string>("Reason");
var banningUser = reader.Get<string>("BanningUser");
var date = reader.Get<string>("Date");
var expiration = reader.Get<string>("Expiration");
yield return new Ban(id, reason, banningUser, date, expiration);
}
}
}
/// <summary>
/// Gets a list of bans sorted by their addition date from newest to oldest
/// </summary>
public List<Ban> GetBans() => GetSortedBans(BanSortMethod.AddedNewestToOldest).ToList();
public List<Ban> GetAllBans() => GetAllBansSorted(BanSortMethod.AddedNewestToOldest).ToList();
/// <summary>
/// Retrieves an enumerable of <see cref="Ban"/> objects, sorted using the provided sort method
/// </summary>
/// <param name="sortMethod"></param>
/// <returns></returns>
public IEnumerable<Ban> GetSortedBans(BanSortMethod sortMethod)
public IEnumerable<Ban> GetAllBansSorted(BanSortMethod sortMethod)
{
List<Ban> banlist = new List<Ban>();
try
{
using (var reader = database.QueryReader("SELECT * FROM Bans"))
var orderBy = SortToOrderByMap[sortMethod];
using (var reader = database.QueryReader($"SELECT * FROM PlayerBans ORDER BY {orderBy}"))
{
while (reader.Read())
{
banlist.Add(new Ban(reader.Get<string>("IP"), reader.Get<string>("Name"), reader.Get<string>("UUID"), reader.Get<string>("AccountName"), reader.Get<string>("Reason"), reader.Get<string>("BanningUser"), reader.Get<string>("Date"), reader.Get<string>("Expiration")));
}
var identifier = reader.Get<string>("Identifier");
var reason = reader.Get<string>("Reason");
var banningUser = reader.Get<string>("BanningUser");
var date = reader.Get<string>("Date");
var expiration = reader.Get<string>("Expiration");
banlist.Sort(new BanComparer(sortMethod));
return banlist;
var ban = new Ban(identifier, reason, banningUser, date, expiration);
banlist.Add(ban);
}
}
return banlist;
}
catch (Exception ex)
{
TShock.Log.Error(ex.ToString());
@ -121,172 +276,14 @@ namespace TShockAPI.DB
}
/// <summary>
/// Gets a ban by name.
/// </summary>
/// <param name="name">The name.</param>
/// <param name="casesensitive">Whether to check with case sensitivity.</param>
/// <returns>The ban.</returns>
public Ban GetBanByName(string name, bool casesensitive = true)
{
try
{
var namecol = casesensitive ? "Name" : "UPPER(Name)";
if (!casesensitive)
name = name.ToUpper();
using (var reader = database.QueryReader("SELECT * FROM Bans WHERE " + namecol + "=@0", name))
{
if (reader.Read())
return new Ban(reader.Get<string>("IP"), reader.Get<string>("Name"), reader.Get<string>("UUID"), reader.Get<string>("AccountName"), reader.Get<string>("Reason"), reader.Get<string>("BanningUser"), reader.Get<string>("Date"), reader.Get<string>("Expiration"));
}
}
catch (Exception ex)
{
TShock.Log.Error(ex.ToString());
}
return null;
}
/// <summary>
/// Gets a ban by account name (not player/character name).
/// </summary>
/// <param name="name">The name.</param>
/// <param name="casesensitive">Whether to check with case sensitivity.</param>
/// <returns>The ban.</returns>
public Ban GetBanByAccountName(string name, bool casesensitive = false)
{
try
{
var namecol = casesensitive ? "AccountName" : "UPPER(AccountName)";
if (!casesensitive)
name = name.ToUpper();
using (var reader = database.QueryReader("SELECT * FROM Bans WHERE " + namecol + "=@0", name))
{
if (reader.Read())
return new Ban(reader.Get<string>("IP"), reader.Get<string>("Name"), reader.Get<string>("UUID"), reader.Get<string>("AccountName"), reader.Get<string>("Reason"), reader.Get<string>("BanningUser"), reader.Get<string>("Date"), reader.Get<string>("Expiration"));
}
}
catch (Exception ex)
{
TShock.Log.Error(ex.ToString());
}
return null;
}
/// <summary>
/// Gets a ban by UUID.
/// </summary>
/// <param name="uuid">The UUID.</param>
/// <returns>The ban.</returns>
public Ban GetBanByUUID(string uuid)
{
try
{
using (var reader = database.QueryReader("SELECT * FROM Bans WHERE UUID=@0", uuid))
{
if (reader.Read())
return new Ban(reader.Get<string>("IP"), reader.Get<string>("Name"), reader.Get<string>("UUID"), reader.Get<string>("AccountName"), reader.Get<string>("Reason"), reader.Get<string>("BanningUser"), reader.Get<string>("Date"), reader.Get<string>("Expiration"));
}
}
catch (Exception ex)
{
TShock.Log.Error(ex.ToString());
}
return null;
}
/// <summary>
/// Adds a ban.
/// </summary>
/// <returns><c>true</c>, if ban was added, <c>false</c> otherwise.</returns>
/// <param name="ip">Ip.</param>
/// <param name="name">Name.</param>
/// <param name="uuid">UUID.</param>
/// <param name="reason">Reason.</param>
/// <param name="exceptions">If set to <c>true</c> enable throwing exceptions.</param>
/// <param name="banner">Banner.</param>
/// <param name="expiration">Expiration date.</param>
public bool AddBan(string ip, string name = "", string uuid = "", string accountName = "", string reason = "", bool exceptions = false, string banner = "", string expiration = "")
{
try
{
/*
* If the ban already exists, update its entry with the new date
* and expiration details.
*/
if (GetBanByIp(ip) != null)
{
return database.Query("UPDATE Bans SET Date = @0, Expiration = @1 WHERE IP = @2", DateTime.UtcNow.ToString("s"), expiration, ip) == 1;
}
else
{
return database.Query("INSERT INTO Bans (IP, Name, UUID, Reason, BanningUser, Date, Expiration, AccountName) VALUES (@0, @1, @2, @3, @4, @5, @6, @7);", ip, name, uuid, reason, banner, DateTime.UtcNow.ToString("s"), expiration, accountName) != 0;
}
}
catch (Exception ex)
{
if (exceptions)
throw ex;
TShock.Log.Error(ex.ToString());
}
return false;
}
/// <summary>
/// Removes a ban.
/// </summary>
/// <returns><c>true</c>, if ban was removed, <c>false</c> otherwise.</returns>
/// <param name="match">Match.</param>
/// <param name="byName">If set to <c>true</c> by name.</param>
/// <param name="casesensitive">If set to <c>true</c> casesensitive.</param>
/// <param name="exceptions">If set to <c>true</c> exceptions.</param>
public bool RemoveBan(string match, bool byName = false, bool casesensitive = true, bool exceptions = false)
{
try
{
if (!byName)
return database.Query("DELETE FROM Bans WHERE IP=@0", match) != 0;
var namecol = casesensitive ? "Name" : "UPPER(Name)";
return database.Query("DELETE FROM Bans WHERE " + namecol + "=@0", casesensitive ? match : match.ToUpper()) != 0;
}
catch (Exception ex)
{
if (exceptions)
throw ex;
TShock.Log.Error(ex.ToString());
}
return false;
}
/// <summary>
/// Removes a ban by account name (not character/player name).
/// </summary>
/// <returns><c>true</c>, if ban was removed, <c>false</c> otherwise.</returns>
/// <param name="match">Match.</param>
/// <param name="casesensitive">If set to <c>true</c> casesensitive.</param>
public bool RemoveBanByAccountName(string match, bool casesensitive = true)
{
try
{
var namecol = casesensitive ? "AccountName" : "UPPER(AccountName)";
return database.Query("DELETE FROM Bans WHERE " + namecol + "=@0", casesensitive ? match : match.ToUpper()) != 0;
}
catch (Exception ex)
{
TShock.Log.Error(ex.ToString());
}
return false;
}
/// <summary>
/// Clears bans.
/// Removes all bans from the database
/// </summary>
/// <returns><c>true</c>, if bans were cleared, <c>false</c> otherwise.</returns>
public bool ClearBans()
{
try
{
return database.Query("DELETE FROM Bans") != 0;
return database.Query("DELETE FROM PlayerBans") != 0;
}
catch (Exception ex)
{
@ -295,19 +292,13 @@ namespace TShockAPI.DB
return false;
}
/// <summary>Removes a ban if it has expired.</summary>
/// <param name="ban">The candidate ban to check.</param>
/// <returns>If the ban has been removed.</returns>
public bool RemoveBanIfExpired(Ban ban)
internal Dictionary<BanSortMethod, string> SortToOrderByMap = new Dictionary<BanSortMethod, string>
{
if (!string.IsNullOrWhiteSpace(ban.Expiration) && (ban.ExpirationDateTime != null) && (DateTime.UtcNow >= ban.ExpirationDateTime))
{
RemoveBan(ban.IP, false, false, false);
return true;
}
return false;
}
{ BanSortMethod.AddedNewestToOldest, "Date DESC" },
{ BanSortMethod.AddedOldestToNewest, "Date ASC" },
{ BanSortMethod.ExpirationSoonestToLatest, "Expiration ASC" },
{ BanSortMethod.ExpirationLatestToSoonest, "Expiration DESC" }
};
}
/// <summary>
@ -334,88 +325,18 @@ namespace TShockAPI.DB
}
/// <summary>
/// An <see cref="IComparer{Ban}"/> used for sorting an enumerable of bans
/// Event args used when a ban check is invoked, or a new ban is added
/// </summary>
public class BanComparer : IComparer<Ban>
public class BanEventArgs : EventArgs
{
private BanSortMethod _method;
/// <summary>
/// Generates a new <see cref="BanComparer"/> using the given <see cref="BanSortMethod"/>
/// The ban being checked or added
/// </summary>
/// <param name="method"></param>
public BanComparer(BanSortMethod method)
{
_method = method;
}
private int CompareDateTimes(DateTime? x, DateTime? y)
{
if (x == null)
{
if (y == null)
{
//If both bans have no BanDateTime they're considered equal
return 0;
}
//If we're sorting by a newest to oldest method, a null value will come after the valid value.
return _method == BanSortMethod.AddedNewestToOldest || _method == BanSortMethod.ExpirationSoonestToLatest ? 1 : -1;
}
if (y == null)
{
return _method == BanSortMethod.AddedNewestToOldest || _method == BanSortMethod.ExpirationSoonestToLatest ? -1 : 1;
}
//Newest to oldest sorting uses x compared to y. Oldest to newest uses y compared to x
return _method == BanSortMethod.AddedNewestToOldest || _method == BanSortMethod.ExpirationSoonestToLatest ? x.Value.CompareTo(y.Value)
: y.Value.CompareTo(x.Value);
}
public Ban Ban { get; set; }
/// <summary>
/// Compares two ban objects
/// Whether or not the operation is valid
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns>1 if x is less than y, 0 if x is equal to y, -1 if x is greater than y</returns>
public int Compare(Ban x, Ban y)
{
if (x == null)
{
if (y == null)
{
return 0;
}
//If Ban y is null and Ban x is not, and we're sorting from newest to oldest, x goes before y. Else y goes before x
return _method == BanSortMethod.AddedNewestToOldest || _method == BanSortMethod.ExpirationSoonestToLatest ? -1 : 1;
}
if (x == null)
{
if (y == null)
{
return 0;
}
//If Ban y is null and Ban x is not, and we're sorting from newest to oldest, x goes before y. Else y goes before x
return _method == BanSortMethod.AddedNewestToOldest || _method == BanSortMethod.ExpirationSoonestToLatest ? -1 : 1;
}
switch (_method)
{
case BanSortMethod.AddedNewestToOldest:
case BanSortMethod.AddedOldestToNewest:
return CompareDateTimes(x.BanDateTime, y.BanDateTime);
case BanSortMethod.ExpirationSoonestToLatest:
case BanSortMethod.ExpirationLatestToSoonest:
return CompareDateTimes(x.ExpirationDateTime, y.ExpirationDateTime);
default:
return 0;
}
}
public bool Valid { get; set; } = true;
}
/// <summary>
@ -424,28 +345,32 @@ namespace TShockAPI.DB
public class Ban
{
/// <summary>
/// Gets or sets the IP address of the ban entry.
/// Contains constants for different identifier types known to TShock
/// </summary>
/// <value>The IP Address</value>
public string IP { get; set; }
public class Identifiers
{
/// <summary>
/// IP identifier prefix constant
/// </summary>
public const string IP = "ip:";
/// <summary>
/// UUID identifier prefix constant
/// </summary>
public const string UUID = "uuid:";
/// <summary>
/// Player name identifier prefix constant
/// </summary>
public const string Name = "name:";
/// <summary>
/// User account identifier prefix constant
/// </summary>
public const string Account = "acc:";
}
/// <summary>
/// Gets or sets the name.
/// An identifiable piece of information to ban
/// </summary>
/// <value>The name.</value>
public string Name { get; set; }
/// <summary>
/// Gets or sets the Client UUID of the ban
/// </summary>
/// <value>The UUID</value>
public string UUID { get; set; }
/// <summary>
/// Gets or sets the account name of the ban
/// </summary>
/// <value>The account name</value>
public String AccountName { get; set; }
public string Identifier { get; set; }
/// <summary>
/// Gets or sets the ban reason.
@ -460,73 +385,46 @@ namespace TShockAPI.DB
public string BanningUser { get; set; }
/// <summary>
/// Gets or sets the UTC date of when the ban is to take effect
/// DateTime from which the ban will take effect
/// </summary>
/// <value>The date, which must be in UTC</value>
public string Date { get; set; }
public DateTime BanDateTime { get; }
/// <summary>
/// Gets the <see cref="System.DateTime"/> object representation of the <see cref="Date"/> string.
/// DateTime at which the ban will end
/// </summary>
public DateTime? BanDateTime { get; }
/// <summary>
/// Gets or sets the expiration date, in which the ban shall be lifted
/// </summary>
/// <value>The expiration.</value>
public string Expiration { get; set; }
/// <summary>
/// Gets the <see cref="System.DateTime"/> object representation of the <see cref="Expiration"/> string.
/// </summary>
public DateTime? ExpirationDateTime { get; }
public DateTime ExpirationDateTime { get; }
/// <summary>
/// Initializes a new instance of the <see cref="TShockAPI.DB.Ban"/> class.
/// </summary>
/// <param name="ip">Ip.</param>
/// <param name="name">Name.</param>
/// <param name="uuid">UUID.</param>
/// <param name="reason">Reason.</param>
/// <param name="banner">Banner.</param>
/// <param name="date">UTC ban date.</param>
/// <param name="exp">Expiration time</param>
public Ban(string ip, string name, string uuid, string accountName, string reason, string banner, string date, string exp)
/// <param name="identifier">Identifier to apply the ban to</param>
/// <param name="reason">Reason for the ban</param>
/// <param name="banningUser">Account name that executed the ban</param>
/// <param name="start">Ban start datetime</param>
/// <param name="end">Ban end datetime</param>
public Ban(string identifier, string reason, string banningUser, string start, string end)
{
IP = ip;
Name = name;
UUID = uuid;
AccountName = accountName;
Identifier = identifier;
Reason = reason;
BanningUser = banner;
Date = date;
Expiration = exp;
BanningUser = banningUser;
DateTime d;
DateTime e;
if (DateTime.TryParse(Date, out d))
if (DateTime.TryParse(start, out DateTime d))
{
BanDateTime = d;
}
if (DateTime.TryParse(Expiration, out e))
else
{
BanDateTime = DateTime.UtcNow;
}
if (DateTime.TryParse(end, out DateTime e))
{
ExpirationDateTime = e;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="TShockAPI.DB.Ban"/> class.
/// </summary>
public Ban()
else
{
IP = string.Empty;
Name = string.Empty;
UUID = string.Empty;
AccountName = string.Empty;
Reason = string.Empty;
BanningUser = string.Empty;
Date = string.Empty;
Expiration = string.Empty;
ExpirationDateTime = DateTime.MaxValue;
}
}
}
}

View file

@ -18,6 +18,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
using System;
using System.Collections.Generic;
using System.Linq;
namespace TShockAPI
{
@ -31,5 +32,25 @@ namespace TShockAPI
foreach (T item in source)
action(item);
}
/// <summary>
/// Attempts to retrieve the value at the given index from the enumerable
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="enumerable"></param>
/// <param name="index"></param>
/// <param name="value"></param>
/// <returns></returns>
public static bool TryGetValue<T>(this IEnumerable<T> enumerable, int index, out T value)
{
if (index < enumerable.Count())
{
value = enumerable.ElementAt(index);
return true;
}
value = default;
return false;
}
}
}

View file

@ -3592,7 +3592,7 @@ namespace TShockAPI
return true;
}
return false;
return true;
}
private static bool HandleKillPortal(GetDataHandlerArgs args)

View file

@ -216,9 +216,13 @@ namespace TShockAPI
Rest.RegisterRedirect("/users/update", "/v2/users/update");
//ban commands
Rest.RegisterRedirect("/bans/list", "/v2/bans/list");
Rest.RegisterRedirect("/bans/read", "/v2/bans/read");
Rest.RegisterRedirect("/bans/destroy", "/v2/bans/destroy");
Rest.RegisterRedirect("/bans/create", "/v3/bans/create");
Rest.RegisterRedirect("/bans/list", "/v3/bans/list");
Rest.RegisterRedirect("/bans/read", "/v3/bans/read");
Rest.RegisterRedirect("/bans/destroy", "/v3/bans/destroy");
Rest.RegisterRedirect("/v2/bans/list", "/v3/bans/list");
Rest.RegisterRedirect("/v2/bans/read", "/v3/bans/read");
Rest.RegisterRedirect("/v2/bans/destroy", "/v3/bans/destroy");
//world commands
Rest.RegisterRedirect("/world/bloodmoon", "v3/world/bloodmoon");
@ -258,10 +262,10 @@ namespace TShockAPI
Rest.Register(new SecureRestCommand("/v2/users/update", UserUpdateV2, RestPermissions.restmanageusers) { DoLog = false });
// Ban Commands
Rest.Register(new SecureRestCommand("/bans/create", BanCreate, RestPermissions.restmanagebans));
Rest.Register(new SecureRestCommand("/v2/bans/list", BanListV2, RestPermissions.restviewbans));
Rest.Register(new SecureRestCommand("/v2/bans/read", BanInfoV2, RestPermissions.restviewbans));
Rest.Register(new SecureRestCommand("/v2/bans/destroy", BanDestroyV2, RestPermissions.restmanagebans));
Rest.Register(new SecureRestCommand("/v3/bans/create", BanCreateV3, RestPermissions.restban, RestPermissions.restmanagebans));
Rest.Register(new SecureRestCommand("/v3/bans/list", BanListV3, RestPermissions.restviewbans));
Rest.Register(new SecureRestCommand("/v3/bans/read", BanInfoV3, RestPermissions.restviewbans));
Rest.Register(new SecureRestCommand("/v3/bans/destroy", BanDestroyV3, RestPermissions.restmanagebans));
// World Commands
Rest.Register(new SecureRestCommand("/world/read", WorldRead));
@ -279,7 +283,6 @@ namespace TShockAPI
Rest.Register(new SecureRestCommand("/v3/players/read", PlayerReadV3, RestPermissions.restuserinfo));
Rest.Register(new SecureRestCommand("/v4/players/read", PlayerReadV4, RestPermissions.restuserinfo));
Rest.Register(new SecureRestCommand("/v2/players/kick", PlayerKickV2, RestPermissions.restkick));
Rest.Register(new SecureRestCommand("/v2/players/ban", PlayerBanV2, RestPermissions.restban, RestPermissions.restmanagebans));
Rest.Register(new SecureRestCommand("/v2/players/kill", PlayerKill, RestPermissions.restkill));
Rest.Register(new SecureRestCommand("/v2/players/mute", PlayerMute, RestPermissions.restmute));
Rest.Register(new SecureRestCommand("/v2/players/unmute", PlayerUnMute, RestPermissions.restmute));
@ -609,115 +612,141 @@ namespace TShockAPI
#region Rest Ban Methods
[Description("Create a new ban entry.")]
[Route("/bans/create")]
[Route("/v3/bans/create")]
[Permission(RestPermissions.restmanagebans)]
[Noun("ip", false, "The IP to ban, at least this or name must be specified.", typeof(String))]
[Noun("name", false, "The name to ban, at least this or ip must be specified.", typeof(String))]
[Noun("identifier", true, "The identifier to ban.", typeof(String))]
[Noun("reason", false, "The reason to assign to the ban.", typeof(String))]
[Noun("start", false, "The datetime at which the ban should start.", typeof(String))]
[Noun("end", false, "The datetime at which the ban should end.", typeof(String))]
[Token]
private object BanCreate(RestRequestArgs args)
private object BanCreateV3(RestRequestArgs args)
{
var ip = args.Parameters["ip"];
var name = args.Parameters["name"];
string identifier = args.Parameters["identifier"];
if (string.IsNullOrWhiteSpace(identifier))
return RestMissingParam("identifier");
if (string.IsNullOrWhiteSpace(ip) && string.IsNullOrWhiteSpace(name))
return RestMissingParam("ip", "name");
string reason = args.Parameters["reason"];
if (string.IsNullOrWhiteSpace(reason))
reason = "Banned";
try
if (!DateTime.TryParse(args.Parameters["start"], out DateTime startDate))
startDate = DateTime.UtcNow;
if (!DateTime.TryParse(args.Parameters["end"], out DateTime endDate))
endDate = DateTime.MaxValue;
if (TShock.Bans.InsertBan(identifier, reason, args.TokenData.Username, startDate, endDate) != null)
{
TShock.Bans.AddBan(ip, name, "", "", args.Parameters["reason"], true, args.TokenData.Username);
}
catch (Exception e)
TSPlayer player = null;
if (identifier.StartsWith(Ban.Identifiers.IP))
{
return RestError(e.Message);
player = TShock.Players.FirstOrDefault(p => p.IP == identifier.Substring(Ban.Identifiers.IP.Length));
}
return RestResponse("Ban created successfully");
else if (identifier.StartsWith(Ban.Identifiers.Name))
{
//Character names may not necessarily be unique, so kick all matches
foreach (var ply in TShock.Players.Where(p => p.Name == identifier.Substring(Ban.Identifiers.Name.Length)))
{
ply.Kick(reason, true);
}
}
else if (identifier.StartsWith(Ban.Identifiers.Account))
{
player = TShock.Players.FirstOrDefault(p => p.Account?.Name == identifier.Substring(Ban.Identifiers.Account.Length));
}
else if (identifier.StartsWith(Ban.Identifiers.UUID))
{
player = TShock.Players.FirstOrDefault(p => p.UUID == identifier.Substring(Ban.Identifiers.UUID.Length));
}
if (player != null)
{
player.Kick(reason, true);
}
return RestResponse("Ban added.");
}
return RestError("Failed to add ban.", status: "500");
}
[Description("Delete an existing ban entry.")]
[Route("/v2/bans/destroy")]
[Route("/v3/bans/destroy")]
[Permission(RestPermissions.restmanagebans)]
[Noun("ban", true, "The search criteria, either an IP address or a name.", typeof(String))]
[Noun("type", true, "The type of search criteria, 'ip' or 'name'. Also used as the method of removing from the database.", typeof(String))]
[Noun("caseinsensitive", false, "Name lookups should be case insensitive.", typeof(bool))]
[Noun("identifier", true, "The identifier of the ban to delete.", typeof(String))]
[Noun("fullDelete", false, "Whether or not to completely remove the ban from the system.", typeof(bool))]
[Token]
private object BanDestroyV2(RestRequestArgs args)
private object BanDestroyV3(RestRequestArgs args)
{
var ret = BanFind(args.Parameters);
if (ret is RestObject)
return ret;
string identifier = args.Parameters["identifier"];
if (string.IsNullOrWhiteSpace(identifier))
return RestMissingParam("identifier");
try
bool.TryParse(args.Parameters["fullDelete"], out bool fullDelete);
if (TShock.Bans.RemoveBan(identifier, fullDelete))
{
Ban ban = (Ban)ret;
switch (args.Parameters["type"])
{
case "ip":
if (!TShock.Bans.RemoveBan(ban.IP, false, false, true))
return RestResponse("Failed to delete ban (already deleted?)");
break;
case "name":
if (!TShock.Bans.RemoveBan(ban.Name, true, GetBool(args.Parameters["caseinsensitive"], true)))
return RestResponse("Failed to delete ban (already deleted?)");
break;
default:
return RestError("Invalid Type: '" + args.Parameters["type"] + "'");
return RestResponse("Ban removed.");
}
}
catch (Exception e)
{
return RestError(e.Message);
}
return RestResponse("Ban deleted successfully");
return RestError("Failed to remove ban.", status: "500");
}
[Description("View the details of a specific ban.")]
[Route("/v2/bans/read")]
[Route("/v3/bans/read")]
[Permission(RestPermissions.restviewbans)]
[Noun("ban", true, "The search criteria, either an IP address or a name.", typeof(String))]
[Noun("type", true, "The type of search criteria, 'ip' or 'name'.", typeof(String))]
[Noun("caseinsensitive", false, "Name lookups should be case insensitive.", typeof(bool))]
[Noun("identifier", true, "The identifier to search for.", typeof(String))]
[Token]
private object BanInfoV2(RestRequestArgs args)
private object BanInfoV3(RestRequestArgs args)
{
var ret = BanFind(args.Parameters);
if (ret is RestObject)
return ret;
string identifier = args.Parameters["identifier"];
if (string.IsNullOrWhiteSpace(identifier))
return RestMissingParam("identifier");
Ban ban = (Ban)ret;
return new RestObject() {
{"name", null == ban.Name ? "" : ban.Name},
{"ip", null == ban.IP ? "" : ban.IP},
{"banning_user", null == ban.BanningUser ? "" : ban.BanningUser},
{"date", null == ban.BanDateTime ? "" : ban.BanDateTime.Value.ToString()},
{"reason", null == ban.Reason ? "" : ban.Reason},
Ban ban = TShock.Bans.GetBanByIdentifier(identifier);
if (ban == null)
{
return RestResponse("No matching bans found.");
}
return new RestObject
{
{"identifier", ban.Identifier },
{"reason", ban.Reason },
{"banning_user", ban.BanningUser },
{"fromDate", ban.BanDateTime.ToString("s") },
{"toDate", ban.ExpirationDateTime.ToString("s") },
};
}
[Description("View all bans in the TShock database.")]
[Route("/v2/bans/list")]
[Route("/v3/bans/list")]
[Permission(RestPermissions.restviewbans)]
[Token]
private object BanListV2(RestRequestArgs args)
private object BanListV3(RestRequestArgs args)
{
IEnumerable<Ban> bans = TShock.Bans.GetAllBans();
var banList = new ArrayList();
foreach (var ban in TShock.Bans.GetBans())
foreach (var ban in bans)
{
banList.Add(
new Dictionary<string, string>
{
{"name", null == ban.Name ? "" : ban.Name},
{"ip", null == ban.IP ? "" : ban.IP},
{"banning_user", null == ban.BanningUser ? "" : ban.BanningUser},
{"date", null == ban.BanDateTime ? "" : ban.BanDateTime.Value.ToString()},
{"reason", null == ban.Reason ? "" : ban.Reason},
{"identifier", ban.Identifier },
{"reason", ban.Reason },
{"banning_user", ban.BanningUser },
{"fromDate", ban.BanDateTime.ToString("s") },
{"toDate", ban.ExpirationDateTime.ToString("s") },
}
);
}
return new RestObject() { { "bans", banList } };
return new RestObject
{
{ "bans", banList }
};
}
#endregion
@ -987,26 +1016,6 @@ namespace TShockAPI
return RestResponse("Player " + player.Name + " was kicked");
}
[Description("Add a ban to the database.")]
[Route("/v2/players/ban")]
[Permission(RestPermissions.restban)]
[Permission(RestPermissions.restmanagebans)]
[Noun("player", true, "The player to kick.", typeof(String))]
[Noun("reason", false, "The reason the user was banned.", typeof(String))]
[Token]
private object PlayerBanV2(RestRequestArgs args)
{
var ret = PlayerFind(args.Parameters);
if (ret is RestObject)
return ret;
TSPlayer player = (TSPlayer)ret;
var reason = null == args.Parameters["reason"] ? "Banned via web" : args.Parameters["reason"];
TShock.Bans.AddBan(player.IP, player.Name, "", "", reason);
player.Kick(reason, true, false, null, true);
return RestResponse("Player " + player.Name + " was banned");
}
[Description("Kill a player.")]
[Route("/v2/players/kill")]
[Permission(RestPermissions.restkill)]
@ -1292,35 +1301,6 @@ namespace TShockAPI
return account;
}
private object BanFind(IParameterCollection parameters)
{
string name = parameters["ban"];
if (string.IsNullOrWhiteSpace(name))
return RestMissingParam("ban");
string type = parameters["type"];
if (string.IsNullOrWhiteSpace(type))
return RestMissingParam("type");
Ban ban;
switch (type)
{
case "ip":
ban = TShock.Bans.GetBanByIp(name);
break;
case "name":
ban = TShock.Bans.GetBanByName(name, GetBool(parameters["caseinsensitive"], true));
break;
default:
return RestError("Invalid Type: '" + type + "'");
}
if (null == ban)
return RestError("Ban " + type + " '" + name + "' doesn't exist");
return ban;
}
private object GroupFind(IParameterCollection parameters)
{
var name = parameters["group"];

View file

@ -1638,9 +1638,13 @@ namespace TShockAPI
return true;
if (force || !HasPermission(Permissions.immunetoban))
{
string ip = IP;
string uuid = UUID;
TShock.Bans.AddBan(ip, Name, uuid, "", reason, false, adminUserName);
TShock.Bans.InsertBan($"{DB.Ban.Identifiers.IP}{IP}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
TShock.Bans.InsertBan($"{DB.Ban.Identifiers.IP}{UUID}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
if (Account != null)
{
TShock.Bans.InsertBan($"{DB.Ban.Identifiers.Account}{Account.Name}", reason, adminUserName, DateTime.UtcNow, DateTime.MaxValue);
}
Disconnect(string.Format("Banned: {0}", reason));
string verb = force ? "force " : "";
if (string.IsNullOrWhiteSpace(adminUserName))

View file

@ -478,26 +478,13 @@ namespace TShockAPI
args.Player.Account.KnownIps = JsonConvert.SerializeObject(KnownIps, Formatting.Indented);
UserAccounts.UpdateLogin(args.Player.Account);
Ban potentialBan = Bans.GetBanByAccountName(args.Player.Account.Name);
//Check if this user has any recorded bans
var validBan = Bans.GetBansByIdentifiers($"acc:{args.Player.Account.Name}", $"uuid:{args.Player.UUID}").FirstOrDefault(b => Bans.IsValidBan(b));
if (potentialBan != null)
//If they do and any are still valid, kick them
if (validBan != null)
{
// A user just signed in successfully despite being banned by account name.
// We should fix the ban database so that all of their ban info is up to date.
Bans.AddBan(args.Player.IP, args.Player.Name, args.Player.UUID, args.Player.Account.Name,
potentialBan.Reason, false, potentialBan.BanningUser, potentialBan.Expiration);
// And then get rid of them.
if (potentialBan.Expiration == "")
{
args.Player.Kick(String.Format("Permanently banned by {0} for {1}", potentialBan.BanningUser
,potentialBan.Reason), true, true);
}
else
{
args.Player.Kick(String.Format("Still banned by {0} for {1}", potentialBan.BanningUser,
potentialBan.Reason), true, true);
}
args.Player.Kick($"You are banned: {validBan.Reason}", true, true);
}
}
@ -1209,36 +1196,17 @@ namespace TShockAPI
return;
}
Ban ban = null;
if (Config.EnableBanOnUsernames)
{
var newban = Bans.GetBanByName(player.Name);
if (null != newban)
ban = newban;
}
if (Config.EnableIPBans && null == ban)
{
ban = Bans.GetBanByIp(player.IP);
}
if (Config.EnableUUIDBans && null == ban && !String.IsNullOrWhiteSpace(player.UUID))
{
ban = Bans.GetBanByUUID(player.UUID);
}
Ban ban = Bans.GetBansByIdentifiers($"name:{player.Name}", $"uuid:{player.UUID}", $"ip:{player.IP}").FirstOrDefault(b => Bans.IsValidBan(b));
if (ban != null)
{
if (!Bans.RemoveBanIfExpired(ban))
if (ban.ExpirationDateTime == DateTime.MaxValue)
{
DateTime exp;
if (!DateTime.TryParse(ban.Expiration, out exp))
{
player.Disconnect("Permanently banned for: " + ban.Reason);
player.Disconnect("You are banned: " + ban.Reason);
}
else
{
TimeSpan ts = exp - DateTime.UtcNow;
TimeSpan ts = ban.ExpirationDateTime - DateTime.UtcNow;
int months = ts.Days / 30;
if (months > 0)
{
@ -1269,7 +1237,6 @@ namespace TShockAPI
args.Handled = true;
}
}
}
/// <summary>OnLeave - Called when a player leaves the server.</summary>
/// <param name="args">args - The LeaveEventArgs object.</param>
@ -1647,7 +1614,8 @@ namespace TShockAPI
player.RecentlyCreatedProjectiles.RemoveAll(p => p.Index == e.number && p.Killed);
}
if (!player.RecentlyCreatedProjectiles.Any(p => p.Index == e.number)) {
if (!player.RecentlyCreatedProjectiles.Any(p => p.Index == e.number))
{
player.RecentlyCreatedProjectiles.Add(new GetDataHandlers.ProjectileStruct()
{
Index = e.number,

View file

@ -226,7 +226,7 @@
</PropertyGroup>
<ProjectExtensions>
<VisualStudio>
<UserProperties BuildVersion_UpdateAssemblyVersion="True" BuildVersion_UpdateFileVersion="True" BuildVersion_BuildAction="Both" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_StartDate="2011/6/17" BuildVersion_IncrementBeforeBuild="False" />
<UserProperties BuildVersion_IncrementBeforeBuild="False" BuildVersion_StartDate="2011/6/17" BuildVersion_BuildVersioningStyle="None.None.None.MonthAndDayStamp" BuildVersion_BuildAction="Both" BuildVersion_UpdateFileVersion="True" BuildVersion_UpdateAssemblyVersion="True" />
</VisualStudio>
</ProjectExtensions>
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.

View file

@ -531,6 +531,11 @@ namespace TShockAPI
{
seconds = 0;
if (string.IsNullOrWhiteSpace(str))
{
return false;
}
var sb = new StringBuilder(3);
for (int i = 0; i < str.Length; i++)
{