From 99b343da0d863f5aeb9f6f6bf34f2585317af2ab Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 27 Apr 2020 19:44:45 -0400 Subject: [PATCH 1/3] Allows joining IRC channels with keys - API bump to 6.1.0. - Store the key colon separated with the channel. --- build/Version.props | 2 +- .../Models/ChatChannel.cs | 3 +- .../Components/Chat/Providers/IrcProvider.cs | 33 ++++++++----- .../Extensions/ChatChannelExtensions.cs | 49 +++++++++++++++++++ 4 files changed, 74 insertions(+), 13 deletions(-) create mode 100644 src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs diff --git a/build/Version.props b/build/Version.props index 7878a900d9..b9cd0eadbd 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,7 +3,7 @@ 4.1.0 - 6.0.0 + 6.1.0 6.0.0 5.0.0 0.4.0 diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs index d2c38df45d..0039e9bc20 100644 --- a/src/Tgstation.Server.Api/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -8,7 +8,8 @@ namespace Tgstation.Server.Api.Models public class ChatChannel { /// - /// The IRC channel name + /// The IRC channel name. Also potentially contains the channel passsword (if separated by a colon). + /// If multiple copies of the same channel with different keys are added to the server, the one that will be used is undefined. /// [StringLength(Limits.MaximumIndexableStringLength)] public string IrcChannel { get; set; } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 2b52d7c47f..c02ee8a0fa 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -9,6 +9,7 @@ using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Extensions; using Tgstation.Server.Host.System; namespace Tgstation.Server.Host.Components.Chat.Providers @@ -376,24 +377,34 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public override Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + public override Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { if (channels.Any(x => x.IrcChannel == null)) throw new InvalidOperationException("ChatChannel missing IrcChannel!"); lock (this) { + var channelsWithKeys = new Dictionary(); var hs = new HashSet(); // for unique inserts - foreach (var I in channels) - hs.Add(I.IrcChannel); - var toPart = new List(); - foreach (var I in client.JoinedChannels) - if (!hs.Remove(I)) - toPart.Add(I); + foreach (var channel in channels) + { + var name = channel.GetIrcChannelName(); + var key = channel.GetIrcChannelKey(); + if (hs.Add(name) && key != null) + channelsWithKeys.Add(name, key); + } - foreach (var I in toPart) - client.RfcPart(I, "Pretty nice abscond!"); - foreach (var I in hs) - client.RfcJoin(I); + var toPart = new List(); + foreach (var activeChannel in client.JoinedChannels) + if (!hs.Remove(activeChannel)) + toPart.Add(activeChannel); + + foreach (var channelToLeave in toPart) + client.RfcPart(channelToLeave, "Pretty nice abscond!"); + foreach (var channelToJoin in hs) + if (channelsWithKeys.TryGetValue(channelToJoin, out var key)) + client.RfcJoin(channelToJoin, key); + else + client.RfcJoin(channelToJoin); return (IReadOnlyCollection)channels.Select(x => { diff --git a/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs b/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs new file mode 100644 index 0000000000..be2afe14bd --- /dev/null +++ b/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.Extensions +{ + /// + /// Extensions for the . + /// + static class ChatChannelExtensions + { + /// + /// Gets the IRC channel name from a given . + /// + /// The to retrieve information from. + /// The IRC channel name stored in the . + public static string GetIrcChannelName(this ChatChannel chatChannel) => GetIrcChannelSplits(chatChannel).First(); + + /// + /// Gets the IRC channel key from a given . + /// + /// The to retrieve information from. + /// The IRC channel key stored in the if it exists, otherwise. + public static string GetIrcChannelKey(this ChatChannel chatChannel) + { + var splits = GetIrcChannelSplits(chatChannel); + if (splits.Count < 2) + return null; + return splits.Last(); + } + + /// + /// Split a given 's . + /// + /// The to work with. + /// A of the 's separated by the ':' . + static IReadOnlyCollection GetIrcChannelSplits(ChatChannel chatChannel) + { + if (chatChannel == null) + throw new ArgumentNullException(nameof(chatChannel)); + + if (chatChannel.IrcChannel == null) + throw new ArgumentException("IrcChannel must be set!", nameof(chatChannel)); + + return chatChannel.IrcChannel.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries); + } + } +} From 7399ac064491c1d67e14f8b5f17981137075fa8c Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 27 Apr 2020 19:49:40 -0400 Subject: [PATCH 2/3] Don't expose the IRC key to the DMAPI --- .../Components/Chat/Providers/IrcProvider.cs | 108 +++++++++--------- 1 file changed, 57 insertions(+), 51 deletions(-) diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index c02ee8a0fa..e4dc424f34 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -377,62 +377,68 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public override Task> MapChannels(IEnumerable channels, CancellationToken cancellationToken) => Task.Factory.StartNew(() => - { - if (channels.Any(x => x.IrcChannel == null)) - throw new InvalidOperationException("ChatChannel missing IrcChannel!"); - lock (this) + public override Task> MapChannels( + IEnumerable channels, + CancellationToken cancellationToken) + => Task.Factory.StartNew(() => { - var channelsWithKeys = new Dictionary(); - var hs = new HashSet(); // for unique inserts - foreach (var channel in channels) + if (channels.Any(x => x.IrcChannel == null)) + throw new InvalidOperationException("ChatChannel missing IrcChannel!"); + lock (this) { - var name = channel.GetIrcChannelName(); - var key = channel.GetIrcChannelKey(); - if (hs.Add(name) && key != null) - channelsWithKeys.Add(name, key); - } - - var toPart = new List(); - foreach (var activeChannel in client.JoinedChannels) - if (!hs.Remove(activeChannel)) - toPart.Add(activeChannel); - - foreach (var channelToLeave in toPart) - client.RfcPart(channelToLeave, "Pretty nice abscond!"); - foreach (var channelToJoin in hs) - if (channelsWithKeys.TryGetValue(channelToJoin, out var key)) - client.RfcJoin(channelToJoin, key); - else - client.RfcJoin(channelToJoin); - - return (IReadOnlyCollection)channels.Select(x => - { - ulong? id = null; - if (!channelIdMap.Any(y => + var channelsWithKeys = new Dictionary(); + var hs = new HashSet(); // for unique inserts + foreach (var channel in channels) { - if (y.Value != x.IrcChannel) - return false; - id = y.Key; - return true; - })) - { - id = channelIdCounter++; - channelIdMap.Add(id.Value, x.IrcChannel); + var name = channel.GetIrcChannelName(); + var key = channel.GetIrcChannelKey(); + if (hs.Add(name) && key != null) + channelsWithKeys.Add(name, key); } - return new ChannelRepresentation - { - RealId = id.Value, - IsAdminChannel = x.IsAdminChannel == true, - ConnectionName = address, - FriendlyName = channelIdMap[id.Value], - IsPrivateChannel = false, - Tag = x.Tag - }; - }).ToList(); - } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); + var toPart = new List(); + foreach (var activeChannel in client.JoinedChannels) + if (!hs.Remove(activeChannel)) + toPart.Add(activeChannel); + + foreach (var channelToLeave in toPart) + client.RfcPart(channelToLeave, "Pretty nice abscond!"); + foreach (var channelToJoin in hs) + if (channelsWithKeys.TryGetValue(channelToJoin, out var key)) + client.RfcJoin(channelToJoin, key); + else + client.RfcJoin(channelToJoin); + + return (IReadOnlyCollection)channels + .Select(x => + { + var channelName = x.GetIrcChannelName(); + ulong? id = null; + if (!channelIdMap.Any(y => + { + if (y.Value != channelName) + return false; + id = y.Key; + return true; + })) + { + id = channelIdCounter++; + channelIdMap.Add(id.Value, channelName); + } + + return new ChannelRepresentation + { + RealId = id.Value, + IsAdminChannel = x.IsAdminChannel == true, + ConnectionName = address, + FriendlyName = channelIdMap[id.Value], + IsPrivateChannel = false, + Tag = x.Tag + }; + }) + .ToList(); + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); /// public override Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) => Task.Factory.StartNew(() => From fd1ff6313f424a11ffab7da1d999922c24288ebb Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Mon, 27 Apr 2020 20:36:05 -0400 Subject: [PATCH 3/3] Remove dumb integration test --- tests/Tgstation.Server.Tests/VersionsTest.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/Tgstation.Server.Tests/VersionsTest.cs b/tests/Tgstation.Server.Tests/VersionsTest.cs index 81bcc68c41..0bc638dec2 100644 --- a/tests/Tgstation.Server.Tests/VersionsTest.cs +++ b/tests/Tgstation.Server.Tests/VersionsTest.cs @@ -60,19 +60,6 @@ namespace Tgstation.Server.Tests Assert.AreEqual(expected, actual); } - [TestMethod] - public void TestClientVersionFollowingApiVersion() - { - var versionString = versionsPropertyGroup.Element(xmlNamespace + "TgsApiVersion").Value + ".0"; - Assert.IsNotNull(versionString); - Assert.IsTrue(Version.TryParse(versionString, out var apiVersion)); - - versionString = versionsPropertyGroup.Element(xmlNamespace + "TgsClientVersion").Value + ".0"; - Assert.IsNotNull(versionString); - Assert.IsTrue(Version.TryParse(versionString, out var clientVersion)); - Assert.IsTrue(clientVersion >= apiVersion); - } - [TestMethod] public void TestWatchdogVersion() {