clean up log reader/make it output more useful message if things go wrong

add unflag as a penalty
show bans/tempbans even after they've expired on penalty list
continue making alias links great again
This commit is contained in:
RaidMax 2019-04-05 13:34:03 -05:00
parent 00634780d4
commit 8ab89e113d
15 changed files with 226 additions and 152 deletions

View File

@ -251,7 +251,6 @@ namespace IW4MAdmin.Application.EventParsers
{ {
CurrentAlias = new EFAlias() CurrentAlias = new EFAlias()
{ {
Active = false,
Name = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors(), Name = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors(),
}, },
NetworkId = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(), NetworkId = regexMatch.Groups[Configuration.Join.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(),
@ -278,7 +277,6 @@ namespace IW4MAdmin.Application.EventParsers
{ {
CurrentAlias = new EFAlias() CurrentAlias = new EFAlias()
{ {
Active = false,
Name = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors() Name = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginName]].ToString().StripColors()
}, },
NetworkId = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(), NetworkId = regexMatch.Groups[Configuration.Quit.GroupMapping[ParserRegex.GroupType.OriginNetworkId]].ToString().ConvertLong(),

View File

@ -262,6 +262,18 @@ namespace IW4MAdmin
else if (E.Type == GameEvent.EventType.Unflag) else if (E.Type == GameEvent.EventType.Unflag)
{ {
var unflagPenalty = new Penalty()
{
Type = Penalty.PenaltyType.Unflag,
Expires = DateTime.UtcNow,
Offender = E.Target,
Offense = E.Data,
Punisher = E.Origin,
When = DateTime.UtcNow,
Link = E.Target.AliasLink
};
await Manager.GetPenaltyService().Create(unflagPenalty);
E.Target.SetLevel(Permission.User, E.Origin); E.Target.SetLevel(Permission.User, E.Origin);
} }
@ -593,6 +605,12 @@ namespace IW4MAdmin
// this are our new connecting clients // this are our new connecting clients
foreach (var client in polledClients[0]) foreach (var client in polledClients[0])
{ {
// note: this prevents players in ZMBI state from being registered with no name
if (string.IsNullOrEmpty(client.Name))
{
continue;
}
var e = new GameEvent() var e = new GameEvent()
{ {
Type = GameEvent.EventType.PreConnect, Type = GameEvent.EventType.PreConnect,

View File

@ -5,55 +5,53 @@ import time
class LogReader(object): class LogReader(object):
def __init__(self): def __init__(self):
self.log_file_sizes = {} self.log_file_sizes = {}
# (if the file changes more than this, ignore ) - 0.125 MB # (if the time between checks is greater, ignore ) - in seconds
self.max_file_size_change = 125000 # self.max_file_time_change = 60
# (if the time between checks is greater, ignore ) - 5 minutes self.max_file_time_change = 10
self.max_file_time_change = 60
def read_file(self, path): def read_file(self, path):
# this removes old entries that are no longer valid
try:
self._clear_old_logs()
except Exception as e:
print('could not clear old logs')
print(e)
# prevent traversing directories # prevent traversing directories
if re.search('r^.+\.\.\\.+$', path): if re.search('r^.+\.\.\\.+$', path):
return False return False
# must be a valid log path and log file # must be a valid log path and log file
if not re.search(r'^.+[\\|\/](.+)[\\|\/].+.log$', path): if not re.search(r'^.+[\\|\/](.+)[\\|\/].+.log$', path):
return False return False
# set the initialze size to the current file size # set the initial size to the current file size
file_size = 0 file_size = 0
# this is the first time the log has been requested
if path not in self.log_file_sizes: if path not in self.log_file_sizes:
self.log_file_sizes[path] = { self.log_file_sizes[path] = {
'length' : self.file_length(path), 'length' : self.file_length(path),
'read': time.time() 'read': time.time()
} }
return True return ''
# grab the previous values # grab the previous values
last_length = self.log_file_sizes[path]['length'] last_length = self.log_file_sizes[path]['length']
last_read = self.log_file_sizes[path]['read']
# the file is being tracked already # the file is being tracked already
new_file_size = self.file_length(path) new_file_size = self.file_length(path)
# the log size was unable to be read (probably the wrong path) # the log size was unable to be read (probably the wrong path)
if new_file_size < 0: if new_file_size < 0:
return False return False
now = time.time()
file_size_difference = new_file_size - last_length file_size_difference = new_file_size - last_length
time_difference = now - last_read
# update the new size and actually read the data # update the new size and actually read the data
self.log_file_sizes[path] = { self.log_file_sizes[path] = {
'length': new_file_size, 'length': new_file_size,
'read': now 'read': time.time()
} }
# if it's been too long since we read and the amount changed is too great, discard it
# todo: do we really want old events? maybe make this an "or"
if file_size_difference > self.max_file_size_change or time_difference > self.max_file_time_change:
return True
new_log_info = self.get_file_lines(path, file_size_difference) new_log_info = self.get_file_lines(path, file_size_difference)
return new_log_info return new_log_info
@ -64,13 +62,23 @@ class LogReader(object):
file_data = file_handle.read(length) file_data = file_handle.read(length)
file_handle.close() file_handle.close()
return file_data.decode('utf-8') return file_data.decode('utf-8')
except: except Exception as e:
print('could not read the log file at {0}, wanted to read {1} bytes'.format(path, length))
print(e)
return False return False
def _clear_old_logs(self):
expired_logs = [path for path in self.log_file_sizes if int(time.time() - self.log_file_sizes[path]['read']) > self.max_file_time_change]
for log in expired_logs:
print('removing expired log {0}'.format(log))
del self.log_file_sizes[log]
def file_length(self, path): def file_length(self, path):
try: try:
return os.stat(path).st_size return os.stat(path).st_size
except: except Exception as e:
print('could not get the size of the log file at {0}'.format(path))
print(e)
return -1 return -1
reader = LogReader() reader = LogReader()

View File

@ -6,14 +6,10 @@ class LogResource(Resource):
def get(self, path): def get(self, path):
path = urlsafe_b64decode(path).decode('utf-8') path = urlsafe_b64decode(path).decode('utf-8')
log_info = reader.read_file(path) log_info = reader.read_file(path)
print(log_info)
if log_info is False:
print('could not read log file ' + path)
empty_read = (log_info == False) or (log_info == True)
return { return {
'success' : log_info is not False, 'success' : log_info is not False,
'length': -1 if empty_read else len(log_info), 'length': 0 if log_info is False else len(log_info),
'data': log_info 'data': log_info
} }

View File

@ -2,10 +2,13 @@ from flask import Flask
from flask_restful import Api from flask_restful import Api
from .log_resource import LogResource from .log_resource import LogResource
from .restart_resource import RestartResource from .restart_resource import RestartResource
import logging
app = Flask(__name__) app = Flask(__name__)
def init(): def init():
log = logging.getLogger('werkzeug')
log.setLevel(logging.ERROR)
api = Api(app) api = Api(app)
api.add_resource(LogResource, '/log/<string:path>') api.add_resource(LogResource, '/log/<string:path>')
api.add_resource(RestartResource, '/restart') api.add_resource(RestartResource, '/restart')

View File

@ -56,10 +56,10 @@ namespace Tests
var warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer }); var warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Console, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(); warnEvent.OnProcessed.Wait();
Assert.True((client.Warnings == 1 || //Assert.True((client.Warnings == 1 ||
warnEvent.Failed) && // warnEvent.Failed) &&
Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1, // Manager.GetPenaltyService().GetClientPenaltiesAsync(client.ClientId).Result.Count(p => p.Type == Penalty.PenaltyType.Warning) == 1,
"warning did not get applied"); // "warning did not get applied");
warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer }); warnEvent = client.Warn("test warn", new EFClient() { ClientId = 1, Level = EFClient.Permission.Banned, CurrentServer = client.CurrentServer });
warnEvent.OnProcessed.Wait(); warnEvent.OnProcessed.Wait();

View File

@ -930,10 +930,8 @@ namespace SharedLibraryCore.Commands
public override async Task ExecuteAsync(GameEvent E) public override async Task ExecuteAsync(GameEvent E)
{ {
var B = await E.Owner.Manager.GetPenaltyService().GetClientPenaltiesAsync(E.Target.ClientId); var existingPenalties = await E.Owner.Manager.GetPenaltyService().GetActivePenaltiesAsync(E.Target.AliasLinkId, E.Target.IPAddress);
var penalty = existingPenalties.FirstOrDefault(b => b.Type > Penalty.PenaltyType.Kick);
var penalty = B.FirstOrDefault(b => b.Type > Penalty.PenaltyType.Kick &&
(b.Expires == null || b.Expires > DateTime.UtcNow));
if (penalty == null) if (penalty == null)
{ {
@ -944,7 +942,7 @@ namespace SharedLibraryCore.Commands
string timeRemaining = penalty.Type == Penalty.PenaltyType.TempBan ? $"({(penalty.Expires.Value - DateTime.UtcNow).TimeSpanText()} remaining)" : ""; string timeRemaining = penalty.Type == Penalty.PenaltyType.TempBan ? $"({(penalty.Expires.Value - DateTime.UtcNow).TimeSpanText()} remaining)" : "";
string success = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_SUCCESS"]; string success = Utilities.CurrentLocalization.LocalizationIndex["COMMANDS_BANINFO_SUCCESS"];
E.Origin.Tell($"^1{E.Target.Name} ^7{string.Format(success, penalty.Punisher.Name)} {penalty.Punisher.Name} {timeRemaining}"); E.Origin.Tell($"^1{E.Target.Name} ^7{string.Format(success, "")} {penalty.Offense} {timeRemaining}");
} }
} }

View File

@ -34,7 +34,6 @@ namespace SharedLibraryCore.Database
AliasId = 1, AliasId = 1,
Active = true, Active = true,
DateAdded = DateTime.UtcNow, DateAdded = DateTime.UtcNow,
IPAddress = 0,
Name = "IW4MAdmin", Name = "IW4MAdmin",
LinkId = 1 LinkId = 1
}; };

View File

@ -25,7 +25,7 @@ namespace SharedLibraryCore.Dtos
public string TimeRemaining => DateTime.UtcNow > Expires ? "" : $"{((Expires ?? DateTime.MaxValue).Year == DateTime.MaxValue.Year ? Utilities.GetTimePassed(TimePunished, true) : Utilities.TimeSpanText((Expires ?? DateTime.MaxValue) - DateTime.UtcNow))}"; public string TimeRemaining => DateTime.UtcNow > Expires ? "" : $"{((Expires ?? DateTime.MaxValue).Year == DateTime.MaxValue.Year ? Utilities.GetTimePassed(TimePunished, true) : Utilities.TimeSpanText((Expires ?? DateTime.MaxValue) - DateTime.UtcNow))}";
public bool Expired => Expires.HasValue && Expires <= DateTime.UtcNow; public bool Expired => Expires.HasValue && Expires <= DateTime.UtcNow;
public DateTime? Expires { get; set; } public DateTime? Expires { get; set; }
public override bool Sensitive => PenaltyType == PenaltyType.Flag; public override bool Sensitive => PenaltyType == PenaltyType.Flag || PenaltyType == PenaltyType.Unflag;
public bool IsEvade { get; set; } public bool IsEvade { get; set; }
public string AdditionalPenaltyInformation => $"{(!string.IsNullOrEmpty(AutomatedOffense) ? $" ({AutomatedOffense})" : "")}{(IsEvade ? $" ({Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PENALTY_EVADE"]})" : "")}"; public string AdditionalPenaltyInformation => $"{(!string.IsNullOrEmpty(AutomatedOffense) ? $" ({AutomatedOffense})" : "")}{(IsEvade ? $" ({Utilities.CurrentLocalization.LocalizationIndex["WEBFRONT_PENALTY_EVADE"]})" : "")}";
} }

View File

@ -571,26 +571,28 @@ namespace SharedLibraryCore.Database.Models
Kick($"{loc["SERVER_TB_REMAIN"]} ({(profileTempBan.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient); Kick($"{loc["SERVER_TB_REMAIN"]} ({(profileTempBan.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient);
return false; return false;
} }
} }
#endregion #endregion
#region CLIENT_LINKED_BAN
// we want to get any penalties that are tied to their IP or AliasLink (but not necessarily their GUID) // we want to get any penalties that are tied to their IP or AliasLink (but not necessarily their GUID)
var activePenalties = await CurrentServer.Manager.GetPenaltyService().GetActivePenaltiesAsync(AliasLinkId, ipAddress); var activePenalties = await CurrentServer.Manager.GetPenaltyService().GetActivePenaltiesAsync(AliasLinkId, ipAddress);
#region CLIENT_LINKED_TEMPBAN
var tempBan = activePenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan); var tempBan = activePenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.TempBan);
// they have an active tempban tied to their AliasLink // they have an active tempban tied to their AliasLink
if (tempBan != null) if (tempBan != null)
{ {
CurrentServer.Logger.WriteDebug($"Kicking {this} because their AliasLink is temporarily banned"); CurrentServer.Logger.WriteDebug($"Tempbanning {this} because their AliasLink is temporarily banned, but they are not");
Kick($"{loc["SERVER_TB_REMAIN"]} ({(tempBan.Expires.Value - DateTime.UtcNow).TimeSpanText()} {loc["WEBFRONT_PENALTY_TEMPLATE_REMAINING"]})", autoKickClient); TempBan(tempBan.Offense, DateTime.UtcNow - (tempBan.Expires ?? DateTime.UtcNow), autoKickClient);
return false; return false;
} }
#endregion
#region CLIENT_LINKED_BAN
var currentBan = activePenalties.FirstOrDefault(p => p.Type == Penalty.PenaltyType.Ban); var currentBan = activePenalties.FirstOrDefault(p => p.Type == Penalty.PenaltyType.Ban);
// they have a perm ban tied to their AliasLink // they have a perm ban tied to their AliasLink/profile
if (currentBan != null) if (currentBan != null)
{ {
CurrentServer.Logger.WriteInfo($"Banned client {this} trying to evade..."); CurrentServer.Logger.WriteInfo($"Banned client {this} trying to evade...");
@ -621,7 +623,20 @@ namespace SharedLibraryCore.Database.Models
} }
#endregion #endregion
else #region CLIENT_LINKED_FLAG
if (Level != Permission.Flagged)
{
var currentFlag = activePenalties.FirstOrDefault(_penalty => _penalty.Type == Penalty.PenaltyType.Flag);
if (currentFlag != null)
{
CurrentServer.Logger.WriteDebug($"Flagging {this} because their AliasLink is flagged, but they are not");
Flag(currentFlag.Offense, autoKickClient);
}
}
#endregion
if (Level == Permission.Flagged)
{ {
var currentAutoFlag = activePenalties var currentAutoFlag = activePenalties
.Where(p => p.Type == Penalty.PenaltyType.Flag && p.PunisherId == 1) .Where(p => p.Type == Penalty.PenaltyType.Flag && p.PunisherId == 1)
@ -629,15 +644,15 @@ namespace SharedLibraryCore.Database.Models
.FirstOrDefault(); .FirstOrDefault();
// remove their auto flag status after a week // remove their auto flag status after a week
if (Level == Permission.Flagged && if (currentAutoFlag != null &&
currentAutoFlag != null &&
(DateTime.UtcNow - currentAutoFlag.When).TotalDays > 7) (DateTime.UtcNow - currentAutoFlag.When).TotalDays > 7)
{ {
Level = Permission.User; CurrentServer.Logger.WriteInfo($"Unflagging {this} because the auto flag time has expired");
Unflag(Utilities.CurrentLocalization.LocalizationIndex["SERVER_AUTOFLAG_UNFLAG"], autoKickClient);
} }
return true;
} }
return true;
} }
[NotMapped] [NotMapped]

View File

@ -15,11 +15,7 @@ namespace SharedLibraryCore.Objects
Ban, Ban,
Unban, Unban,
Any, Any,
} Unflag
public String GetWhenFormatted()
{
return When.ToString("MM/dd/yy HH:mm:ss"); ;
} }
} }
} }

View File

@ -17,22 +17,64 @@ namespace SharedLibraryCore.Services
{ {
using (var context = new DatabaseContext()) using (var context = new DatabaseContext())
{ {
int? linkId = null;
int? aliasId = null;
if (entity.IPAddress != null)
{
var existingAlias = await context.Aliases
.Select(_alias => new { _alias.AliasId, _alias.LinkId, _alias.IPAddress, _alias.Name })
.FirstOrDefaultAsync(_alias => _alias.IPAddress == entity.IPAddress);
if (existingAlias != null)
{
linkId = existingAlias.LinkId;
if (existingAlias.Name == entity.Name)
{
aliasId = existingAlias.AliasId;
}
}
}
var client = new EFClient() var client = new EFClient()
{ {
Level = Permission.User, Level = Permission.User,
FirstConnection = DateTime.UtcNow, FirstConnection = DateTime.UtcNow,
LastConnection = DateTime.UtcNow, LastConnection = DateTime.UtcNow,
NetworkId = entity.NetworkId, NetworkId = entity.NetworkId
AliasLink = new EFAliasLink()
}; };
client.CurrentAlias = new Alias() if (linkId.HasValue)
{ {
Name = entity.Name, client.AliasLinkId = linkId.Value;
Link = client.AliasLink, }
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress, else
}; {
client.AliasLink = new EFAliasLink();
}
if (aliasId.HasValue)
{
client.CurrentAliasId = aliasId.Value;
}
else
{
client.CurrentAlias = new Alias()
{
Name = entity.Name,
DateAdded = DateTime.UtcNow,
IPAddress = entity.IPAddress,
Link = client.AliasLink,
};
if (client.CurrentAlias.Link == null)
{
client.CurrentAlias.LinkId = linkId.Value;
}
}
context.Clients.Add(client); context.Clients.Add(client);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
@ -48,13 +90,21 @@ namespace SharedLibraryCore.Services
var iqAliases = context.Aliases var iqAliases = context.Aliases
.Include(a => a.Link) .Include(a => a.Link)
// we only want alias that have the same IP address or share a link // we only want alias that have the same IP address or share a link
.Where(_alias => _alias.IPAddress == ip || (_alias.LinkId == entity.AliasLinkId && _alias.Active)); .Where(_alias => _alias.IPAddress == ip || (_alias.LinkId == entity.AliasLinkId));
#if DEBUG == true #if DEBUG == true
var aliasSql = iqAliases.ToSql(); var aliasSql = iqAliases.ToSql();
#endif #endif
var aliases = await iqAliases.ToListAsync(); var aliases = await iqAliases.ToListAsync();
// update each of the aliases where this is no IP but the name is identical
foreach (var alias in aliases.Where(_alias => (_alias.IPAddress == null || _alias.IPAddress == 0)))
{
alias.IPAddress = ip;
}
await context.SaveChangesAsync();
// see if they have a matching IP + Name but new NetworkId // see if they have a matching IP + Name but new NetworkId
var existingExactAlias = aliases.FirstOrDefault(a => a.Name == name && a.IPAddress == ip); var existingExactAlias = aliases.FirstOrDefault(a => a.Name == name && a.IPAddress == ip);
bool hasExactAliasMatch = existingExactAlias != null; bool hasExactAliasMatch = existingExactAlias != null;
@ -82,7 +132,7 @@ namespace SharedLibraryCore.Services
// update all previous aliases // update all previous aliases
await context.Aliases await context.Aliases
.Where(_alias => _alias.LinkId == oldAliasLink.AliasLinkId) .Where(_alias => _alias.LinkId == oldAliasLink.AliasLinkId)
.ForEachAsync(_alias => { _alias.LinkId = newAliasLink.AliasLinkId; _alias.Active = true; }); .ForEachAsync(_alias => _alias.LinkId = newAliasLink.AliasLinkId);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
// we want to delete the now inactive alias // we want to delete the now inactive alias
@ -109,62 +159,23 @@ namespace SharedLibraryCore.Services
} }
// theres no exact match, but they've played before with the GUID or IP // theres no exact match, but they've played before with the GUID or IP
else if (hasExistingAlias) else
{ {
entity.CurrentServer.Logger.WriteDebug($"Connecting player is using a new alias {entity}"); entity.CurrentServer.Logger.WriteDebug($"Connecting player is using a new alias {entity}");
// this happens when a temporary alias gets updated var newAlias = new EFAlias()
if (entity.CurrentAlias.Name == name && entity.CurrentAlias.IPAddress == null)
{ {
entity.CurrentAlias.IPAddress = ip; DateAdded = DateTime.UtcNow,
await context.SaveChangesAsync(); IPAddress = ip,
} LinkId = newAliasLink.AliasLinkId,
Name = name,
else Active = true,
{ };
var newAlias = new EFAlias()
{
DateAdded = DateTime.UtcNow,
IPAddress = ip,
LinkId = newAliasLink.AliasLinkId,
Name = name,
Active = true,
};
entity.CurrentAlias = newAlias;
await context.SaveChangesAsync();
}
}
// no record of them playing
else
{
entity.AliasLink.Active = true;
entity.CurrentAlias.Active = true;
entity.CurrentAlias.IPAddress = ip;
entity.CurrentAlias.Name = name;
entity.CurrentAlias = newAlias;
entity.CurrentAliasId = 0;
await context.SaveChangesAsync(); await context.SaveChangesAsync();
} }
//var linkIds = aliases.Select(a => a.LinkId);
//if (linkIds.Count() > 0 &&
// aliases.Count(_alias => _alias.Name == name && _alias.IPAddress == ip) > 0)
//{
// var highestLevel = await context.Clients
// .Where(c => linkIds.Contains(c.AliasLinkId))
// .MaxAsync(c => c.Level);
// if (entity.Level != highestLevel)
// {
// entity.CurrentServer.Logger.WriteDebug($"{entity} updating user level");
// // todo: log level changes here
// context.Update(entity);
// entity.SetLevel(highestLevel, Utilities.IW4MAdminClient(entity.CurrentServer));
// await context.SaveChangesAsync();
// }
//}
} }
/// <summary> /// <summary>
@ -183,40 +194,51 @@ namespace SharedLibraryCore.Services
.Where(_client => _client.AliasLinkId == temporalClient.AliasLinkId) .Where(_client => _client.AliasLinkId == temporalClient.AliasLinkId)
.FirstAsync(); .FirstAsync();
var oldPermission = entity.Level;
entity.Level = newPermission;
await ctx.SaveChangesAsync();
#if DEBUG == true
temporalClient.CurrentServer.Logger.WriteDebug($"Updated {temporalClient.ClientId} to {newPermission}");
#endif
// if their permission level has been changed to level that needs to be updated on all accounts // if their permission level has been changed to level that needs to be updated on all accounts
if ((entity.Level != newPermission) && if ((oldPermission != newPermission) &&
(newPermission == Permission.Banned || (newPermission == Permission.Banned ||
newPermission == Permission.Flagged || newPermission == Permission.Flagged ||
newPermission == Permission.User)) newPermission == Permission.User))
{ {
var changeSvc = new ChangeHistoryService(); var changeSvc = new ChangeHistoryService();
// get all clients that have the same linkId //get all clients that have the same linkId
var iqMatchingClients = ctx.Clients var iqMatchingClients = ctx.Clients
.Where(_client => _client.AliasLinkId == entity.AliasLinkId) .Where(_client => _client.AliasLinkId == entity.AliasLinkId);
// make sure we don't select ourselves twice // make sure we don't select ourselves twice
.Where(_client => _client.ClientId != temporalClient.ClientId); //.Where(_client => _client.ClientId != temporalClient.ClientId);
var matchingClients = await iqMatchingClients.ToListAsync();
// this updates the level for all the clients with the same LinkId // this updates the level for all the clients with the same LinkId
// only if their new level is flagged or banned // only if their new level is flagged or banned
foreach (var client in matchingClients) await iqMatchingClients.ForEachAsync(async (_client) =>
{ {
client.Level = newPermission; _client.Level = newPermission;
// hack this saves our change to the change history log // hack this saves our change to the change history log
await changeSvc.Add(new GameEvent() await changeSvc.Add(new GameEvent()
{ {
Type = GameEvent.EventType.ChangePermission, Type = GameEvent.EventType.ChangePermission,
Extra = newPermission, Extra = newPermission,
Origin = origin, Origin = origin,
Target = client Target = _client
}, ctx); }, ctx);
} #if DEBUG == true
} temporalClient.CurrentServer.Logger.WriteDebug($"Updated linked {_client.ClientId} to {newPermission}");
#endif
});
entity.Level = newPermission;
await ctx.SaveChangesAsync(); await ctx.SaveChangesAsync();
}
} }
temporalClient.Level = newPermission; temporalClient.Level = newPermission;
@ -346,13 +368,6 @@ namespace SharedLibraryCore.Services
// update in database // update in database
await context.SaveChangesAsync(); await context.SaveChangesAsync();
// this is set so future updates don't trigger a new alias add
if (temporalClient.CurrentAlias.AliasId == 0)
{
temporalClient.CurrentAlias.AliasId = entity.CurrentAlias.AliasId;
}
return entity; return entity;
} }
} }

View File

@ -8,7 +8,6 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Linq.Expressions; using System.Linq.Expressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using static SharedLibraryCore.Database.Models.EFClient;
namespace SharedLibraryCore.Services namespace SharedLibraryCore.Services
{ {
@ -35,6 +34,35 @@ namespace SharedLibraryCore.Services
newEntity.Offender.ReceivedPenalties?.Add(penalty); newEntity.Offender.ReceivedPenalties?.Add(penalty);
context.Penalties.Add(penalty); context.Penalties.Add(penalty);
await context.SaveChangesAsync(); await context.SaveChangesAsync();
// certain penalties we want to save across all profiles
if (penalty.Type.ShouldPenaltyApplyToAllProfiles())
{
var iqLinkedProfiles = context.Clients
.Where(_client => _client.AliasLinkId == newEntity.Link.AliasLinkId)
// prevent adding the penalty twice to the same profile
.Where(_client => _client.ClientId != penalty.OffenderId);
await iqLinkedProfiles.ForEachAsync(_client =>
{
var linkedPenalty = new EFPenalty()
{
OffenderId = _client.ClientId,
PunisherId = newEntity.Punisher.ClientId,
LinkId = newEntity.Link.AliasLinkId,
Type = newEntity.Type,
Expires = newEntity.Expires,
Offense = newEntity.Offense,
When = DateTime.UtcNow,
AutomatedOffense = newEntity.AutomatedOffense,
IsEvadedOffense = newEntity.IsEvadedOffense
};
context.Penalties.Add(linkedPenalty);
});
await context.SaveChangesAsync();
}
} }
return newEntity; return newEntity;
@ -70,26 +98,12 @@ namespace SharedLibraryCore.Services
throw new NotImplementedException(); throw new NotImplementedException();
} }
public async Task<IList<EFPenalty>> GetClientPenaltiesAsync(int clientId)
{
using (var context = new DatabaseContext(true))
{
return await context.Penalties
.Where(p => p.OffenderId == clientId)
.Where(p => p.Active)
.Include(p => p.Offender.CurrentAlias)
.Include(p => p.Punisher.CurrentAlias)
.ToListAsync();
}
}
public async Task<IList<PenaltyInfo>> GetRecentPenalties(int count, int offset, Penalty.PenaltyType showOnly = Penalty.PenaltyType.Any) public async Task<IList<PenaltyInfo>> GetRecentPenalties(int count, int offset, Penalty.PenaltyType showOnly = Penalty.PenaltyType.Any)
{ {
using (var context = new DatabaseContext(true)) using (var context = new DatabaseContext(true))
{ {
var iqPenalties = context.Penalties var iqPenalties = context.Penalties
.Where(p => showOnly == Penalty.PenaltyType.Any ? p.Type != Penalty.PenaltyType.Any : p.Type == showOnly) .Where(p => showOnly == Penalty.PenaltyType.Any ? p.Type != Penalty.PenaltyType.Any : p.Type == showOnly)
.Where(p => p.Active)
.OrderByDescending(p => p.When) .OrderByDescending(p => p.When)
.Skip(offset) .Skip(offset)
.Take(count) .Take(count)
@ -129,7 +143,7 @@ namespace SharedLibraryCore.Services
using (var ctx = new DatabaseContext(true)) using (var ctx = new DatabaseContext(true))
{ {
var iqPenalties = ctx.Penalties.AsNoTracking() var iqPenalties = ctx.Penalties.AsNoTracking()
.Where(_penalty => _penalty.Active) //.Where(_penalty => _penalty.Active)
.Where(_penalty => _penalty.OffenderId == clientId || _penalty.PunisherId == clientId) .Where(_penalty => _penalty.OffenderId == clientId || _penalty.PunisherId == clientId)
.Where(_penalty => _penalty.When < startAt) .Where(_penalty => _penalty.When < startAt)
.OrderByDescending(_penalty => _penalty.When) .OrderByDescending(_penalty => _penalty.When)
@ -159,7 +173,7 @@ namespace SharedLibraryCore.Services
} }
} }
public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int linkId, int? ip = null) public async Task<List<EFPenalty>> GetActivePenaltiesAsync(int linkId, int? ip = null, bool includePunisherName = false)
{ {
var now = DateTime.UtcNow; var now = DateTime.UtcNow;
@ -210,7 +224,7 @@ namespace SharedLibraryCore.Services
await penalties.ForEachAsync(p => await penalties.ForEachAsync(p =>
{ {
p.Active = false; p.Active = false;
}); });
await context.SaveChangesAsync(); await context.SaveChangesAsync();

View File

@ -14,6 +14,7 @@ using System.Reflection;
using System.Text; using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using static SharedLibraryCore.Objects.Penalty;
using static SharedLibraryCore.Server; using static SharedLibraryCore.Server;
namespace SharedLibraryCore namespace SharedLibraryCore
@ -478,6 +479,15 @@ namespace SharedLibraryCore
return "unknown"; return "unknown";
} }
public static bool ShouldPenaltyApplyToAllProfiles(this PenaltyType penaltyType)
{
return penaltyType == PenaltyType.Ban ||
penaltyType == PenaltyType.Unban ||
penaltyType == PenaltyType.Flag ||
penaltyType == PenaltyType.Unflag ||
penaltyType == PenaltyType.TempBan;
}
/// <summary> /// <summary>
/// Helper extension that determines if a user is a privileged client /// Helper extension that determines if a user is a privileged client
/// </summary> /// </summary>

View File

@ -101,6 +101,10 @@
color: rgba(116, 147, 99, 1); color: rgba(116, 147, 99, 1);
} }
.penalties-color-unflag {
color: rgb(140, 154, 132);
}
.penalties-color-report { .penalties-color-report {
color: #b3ae8f; color: #b3ae8f;
} }