From 4268e4d7d950bbec33d05bfebd0f8afe8504da0b Mon Sep 17 00:00:00 2001 From: snow flurry Date: Sat, 21 Oct 2023 13:46:56 -0700 Subject: [PATCH] Add AMI integration --- PhoneToolMX.Models/ViewModels/ExtensionVM.cs | 2 ++ PhoneToolMX.Models/ViewModels/IViewModel.cs | 4 ++- PhoneToolMX.Models/ViewModels/PhoneVM.cs | 7 +++- PhoneToolMX/Controllers/BaseController.cs | 19 +++++++++-- PhoneToolMX/Program.cs | 6 ++++ PhoneToolMX/Services/AsteriskManager.cs | 36 ++++++++++++++++++++ PhoneToolMX/Services/BadResponseException.cs | 7 ++++ PhoneToolMX/Services/IAsteriskManager.cs | 7 ++++ PhoneToolMX/Services/PJSIPNotifyAction.cs | 25 ++++++++++++++ 9 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 PhoneToolMX/Services/AsteriskManager.cs create mode 100644 PhoneToolMX/Services/BadResponseException.cs create mode 100644 PhoneToolMX/Services/IAsteriskManager.cs create mode 100644 PhoneToolMX/Services/PJSIPNotifyAction.cs diff --git a/PhoneToolMX.Models/ViewModels/ExtensionVM.cs b/PhoneToolMX.Models/ViewModels/ExtensionVM.cs index 35067d9..f6b6f69 100644 --- a/PhoneToolMX.Models/ViewModels/ExtensionVM.cs +++ b/PhoneToolMX.Models/ViewModels/ExtensionVM.cs @@ -53,5 +53,7 @@ namespace PhoneToolMX.Models.ViewModels HoldMusic = extEnt.HoldMusic?.Id, }; } + // TODO: fix hack + public string NotifyOnChange() => (ExtId == 0 ? Id + 1000 : ExtId).ToString(); } } diff --git a/PhoneToolMX.Models/ViewModels/IViewModel.cs b/PhoneToolMX.Models/ViewModels/IViewModel.cs index 0909369..1ed95af 100644 --- a/PhoneToolMX.Models/ViewModels/IViewModel.cs +++ b/PhoneToolMX.Models/ViewModels/IViewModel.cs @@ -6,10 +6,12 @@ namespace PhoneToolMX.Models.ViewModels public interface IViewModel { public int? Id { get; set; } - + public IOwnedModel ToEntity(PTMXContext ctx); public IOwnedModel ToEntity(PTMXContext ctx, IOwnedModel current); public IViewModel FromEntity(IOwnedModel entity); + + public string NotifyOnChange(); } } diff --git a/PhoneToolMX.Models/ViewModels/PhoneVM.cs b/PhoneToolMX.Models/ViewModels/PhoneVM.cs index 67df3b9..56a9f31 100644 --- a/PhoneToolMX.Models/ViewModels/PhoneVM.cs +++ b/PhoneToolMX.Models/ViewModels/PhoneVM.cs @@ -8,7 +8,7 @@ namespace PhoneToolMX.Models.ViewModels public class PhoneVM : IViewModel { public int? Id { get; set; } - + [Header("MAC Address")] [Required] [RegularExpression("(?:[0-9a-fA-F]{2}[-:]?){6}", ErrorMessage = "Must be of the form XX:XX:XX:XX:XX:XX, XX-XX-XX-XX-XX-XX, or XXXXXXXXXXXX")] @@ -66,5 +66,10 @@ namespace PhoneToolMX.Models.ViewModels PrimaryExtension = phoneEnt.PrimaryExtension?.Id, }; } + + // a bit of a hack, but we can just choose any extension that belongs to this phone + public string NotifyOnChange() => Extensions.Count > 0 + ? (Extensions.First() + 1000).ToString() + : null; } } diff --git a/PhoneToolMX/Controllers/BaseController.cs b/PhoneToolMX/Controllers/BaseController.cs index b7c5622..28950df 100644 --- a/PhoneToolMX/Controllers/BaseController.cs +++ b/PhoneToolMX/Controllers/BaseController.cs @@ -1,3 +1,4 @@ +using AsterNET.Manager; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; @@ -5,6 +6,7 @@ using PhoneToolMX.Data; using PhoneToolMX.Helpers; using PhoneToolMX.Models; using PhoneToolMX.Models.ViewModels; +using PhoneToolMX.Services; using System.Security.Authentication; using System.Security.Claims; @@ -118,7 +120,7 @@ namespace PhoneToolMX.Controllers SetMessage( FormMessageType.Success, - $"{typeof(T).Name} {GetFriendlyName(new TViewModel().FromEntity(entity.Entity))} was created." + $"{typeof(T).Name} {GetFriendlyName(entity.Entity)} was created." ); return RedirectToAction("Edit", new { id = entity.Entity.Id }); @@ -170,9 +172,22 @@ namespace PhoneToolMX.Controllers _context.Set().Update(currentModel); await _context.SaveChangesAsync(); + // Try to notify the relevant endpoint to update its config + var amiStatus = ""; + if (HttpContext.RequestServices.GetService() is {} ami) { + try { await ami.SendNotifyAsync(vm.NotifyOnChange()); + amiStatus = "Your phone's configuration should be updated shortly."; + } + catch (BadResponseException e) { amiStatus = $"You'll need to manually update your phone settings (AMI Response: {e.Message})."; } + catch (ManagerException e) { amiStatus = $"You'll need to manually update your phone settings (AMI Client Error: {e.Message})."; } + catch (Exception e) { + amiStatus = $"You'll need to manually update your phone settings (Unknown Error: {e.Message})."; + } + } + SetMessage( FormMessageType.Success, - $"{typeof(T).Name} {GetFriendlyName(vm)} was updated." + $"{typeof(T).Name} {GetFriendlyName(currentModel)} was updated.{" " + amiStatus}" ); return RedirectToAction("Edit", new { id = entity.Id }); diff --git a/PhoneToolMX/Program.cs b/PhoneToolMX/Program.cs index e910072..28cebc4 100644 --- a/PhoneToolMX/Program.cs +++ b/PhoneToolMX/Program.cs @@ -10,6 +10,7 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using NuGet.Packaging; using PhoneToolMX.Models; +using PhoneToolMX.Services; using System.Net; using System.Security.Authentication; using System.Security.Claims; @@ -112,6 +113,11 @@ builder.Services.AddAuthorization(opts => policy => policy.RequireRole("Administrator")); }); +// AMI for PJSIP notifications +if (builder.Configuration.GetSection("ami").Exists()) { + builder.Services.AddSingleton(); +} + var app = builder.Build(); // Configure the HTTP request pipeline. diff --git a/PhoneToolMX/Services/AsteriskManager.cs b/PhoneToolMX/Services/AsteriskManager.cs new file mode 100644 index 0000000..7ad1539 --- /dev/null +++ b/PhoneToolMX/Services/AsteriskManager.cs @@ -0,0 +1,36 @@ +using System.Collections; +using System.Net.Security; +using System.Net.Sockets; +using System.Text; +using AsterNET.Manager; + +namespace PhoneToolMX.Services +{ + public class AsteriskManager : IAsteriskManager + { + private IConfigurationSection amiConf; + private ManagerConnection conn; + public AsteriskManager(IConfiguration config) + { + amiConf = config.GetRequiredSection("ami"); + conn = new ManagerConnection(amiConf["host"], + int.Parse(amiConf["port"]), + amiConf["username"], + amiConf["secret"]); + conn.Login(); + } + + /// + /// Sends a polycom-check-cfg notification to the requested PJSIP endpoint. + /// + /// The PJSIP endpoint to notify, without the PJSIP/ prefix. + /// If the request fails, this exception will be thrown. + public async Task SendNotifyAsync(string endpoint) + { + var response = await conn.SendActionAsync(new PJSIPNotifyAction(endpoint, "polycom-check-cfg")); + if (!response.IsSuccess()) { + throw new BadResponseException(response.Message); + } + } + } +} diff --git a/PhoneToolMX/Services/BadResponseException.cs b/PhoneToolMX/Services/BadResponseException.cs new file mode 100644 index 0000000..e140c0d --- /dev/null +++ b/PhoneToolMX/Services/BadResponseException.cs @@ -0,0 +1,7 @@ +namespace PhoneToolMX.Services +{ + public class BadResponseException : Exception + { + public BadResponseException(string message) : base(message) {} + } +} diff --git a/PhoneToolMX/Services/IAsteriskManager.cs b/PhoneToolMX/Services/IAsteriskManager.cs new file mode 100644 index 0000000..fff7f39 --- /dev/null +++ b/PhoneToolMX/Services/IAsteriskManager.cs @@ -0,0 +1,7 @@ +namespace PhoneToolMX.Services +{ + public interface IAsteriskManager + { + public Task SendNotifyAsync(string endpoint); + } +} diff --git a/PhoneToolMX/Services/PJSIPNotifyAction.cs b/PhoneToolMX/Services/PJSIPNotifyAction.cs new file mode 100644 index 0000000..775397d --- /dev/null +++ b/PhoneToolMX/Services/PJSIPNotifyAction.cs @@ -0,0 +1,25 @@ +namespace PhoneToolMX.Services +{ + public class PJSIPNotifyAction : AsterNET.Manager.Action.ManagerAction + { + + public override string Action => "PJSIPNotify"; + + /// + /// The endpoint to which to send the NOTIFY. + /// + public string Endpoint { get; set; } + + /// + /// The config section name from pjsip_notify.conf to use. + /// One of Option or Variable must be specified. + /// + public string Option { get; set; } + + public PJSIPNotifyAction(string endpoint, string option) + { + Endpoint = endpoint; + Option = option; + } + } +}