/* * * (c) Copyright Ascensio System Limited 2010-2018 * * This program is freeware. You can redistribute it and/or modify it under the terms of the GNU * General Public License (GPL) version 3 as published by the Free Software Foundation (https://www.gnu.org/copyleft/gpl.html). * In accordance with Section 7(a) of the GNU GPL its Section 15 shall be amended to the effect that * Ascensio System SIA expressly excludes the warranty of non-infringement of any third-party rights. * * THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY; WITHOUT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY OR * FITNESS FOR A PARTICULAR PURPOSE. For more details, see GNU GPL at https://www.gnu.org/copyleft/gpl.html * * You can contact Ascensio System SIA by email at sales@onlyoffice.com * * The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display * Appropriate Legal Notices, as required under Section 5 of the GNU GPL version 3. * * Pursuant to Section 7 § 3(b) of the GNU GPL you must retain the original ONLYOFFICE logo which contains * relevant author attributions when distributing the software. If the display of the logo in its graphic * form is not reasonably feasible for technical reasons, you must include the words "Powered by ONLYOFFICE" * in every copy of the program you distribute. * Pursuant to Section 7 § 3(e) we decline to grant you any rights under trademark law for use of our trademarks. * */ using ASC.Common; using ASC.Common.Logging; using ASC.Core; using ASC.Core.Common.Configuration; using ASC.Core.Notify.Signalr; using ASC.CRM.Core; using ASC.CRM.Core.Dao; using ASC.CRM.Core.Entities; using ASC.VoipService; using ASC.VoipService.Twilio; using ASC.Web.Studio.Utility; using Autofac; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.Options; using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Text; using System.Web; using System.Web.Http; using Twilio.AspNet.Common; using Twilio.AspNet.Core; using Twilio.TwiML; namespace ASC.Web.CRM.Classes { [ServiceFilter(typeof(ValidateRequestFilter))] public class TwilioController : ApiController { public TwilioController(IOptionsMonitor logger, DaoFactory daoFactory, VoipEngine voipEngine, TenantManager tenantManager, SecurityContext securityContext) { TenantManager = tenantManager; Log = logger.Get("ASC"); DaoFactory = daoFactory; VoipEngine = voipEngine; SecurityContext = securityContext; } public SecurityContext SecurityContext { get; } public TenantManager TenantManager { get; } public VoipEngine VoipEngine { get; } public DaoFactory DaoFactory { get; } private readonly ILog Log; private static readonly object LockObj = new object(); [System.Web.Http.HttpPost] public HttpResponseMessage Index(TwilioVoiceRequest request, [FromUri]Guid? callerId = null, [FromUri]int contactId = 0) { try { lock (LockObj) { request.AddAdditionalFields(callerId, contactId); var response = request.IsInbound ? Inbound(request, DaoFactory) : Outbound(request, DaoFactory); return GetHttpResponse(response); } } catch (Exception e) { Log.Error(e); throw; } } [System.Web.Http.HttpPost] public HttpResponseMessage Client(TwilioVoiceRequest request, [FromUri]Guid callerId) { try { request.AddAdditionalFields(callerId); VoipEngine.SaveOrUpdateCall(CallFromTwilioRequest(request)); return GetHttpResponse(new VoiceResponse()); } catch (Exception e) { Log.Error(e); throw; } } [System.Web.Http.HttpPost] public HttpResponseMessage Dial(TwilioVoiceRequest request, [FromUri]Guid callerId, [FromUri]int contactId = 0, [FromUri]string reject = null) { try { request.AddAdditionalFields(callerId, contactId, reject); var call = CallFromTwilioRequest(request); call = VoipEngine.SaveOrUpdateCall(call); var parentCall = DaoFactory.GetVoipDao().GetCall(call.ParentID); if (!string.IsNullOrEmpty(request.RecordingSid)) { if (parentCall.VoipRecord == null || string.IsNullOrEmpty(parentCall.VoipRecord.Id)) { parentCall.VoipRecord = new VoipRecord { Id = request.RecordingSid }; } DaoFactory.GetVoipDao().SaveOrUpdateCall(parentCall); } VoipEngine.SaveAdditionalInfo(parentCall.Id); return GetHttpResponse(request.Dial()); } catch (Exception e) { Log.Error(e); throw; } } [System.Web.Http.HttpPost] public HttpResponseMessage Enqueue(TwilioVoiceRequest request, [FromUri]Guid? callerId = null, [FromUri]int contactId = 0) { try { request.AddAdditionalFields(callerId, contactId); if (request.QueueResult != "bridged" && request.QueueResult != "redirected") { MissCall(request, VoipEngine); } return GetHttpResponse(request.Enqueue(request.QueueResult)); } catch (Exception e) { Log.Error(e); throw; } } [System.Web.Http.HttpPost] public HttpResponseMessage Queue(TwilioVoiceRequest request, [FromUri]Guid? callerId = null, [FromUri]int contactId = 0) { try { request.AddAdditionalFields(callerId, contactId); return GetHttpResponse(request.Queue()); } catch (Exception e) { Log.Error(e); throw; } } [System.Web.Http.HttpPost] public HttpResponseMessage Dequeue(TwilioVoiceRequest request, [FromUri]Guid? callerId = null, [FromUri]int contactId = 0, [FromUri]string reject = "") { try { request.AddAdditionalFields(callerId, contactId, reject); if (Convert.ToBoolean(request.Reject)) { MissCall(request, VoipEngine); return GetHttpResponse(request.Leave()); } VoipEngine.AnswerCall(CallFromTwilioRequest(request)); return GetHttpResponse(request.Dequeue()); } catch (Exception e) { Log.Error(e); throw; } } [System.Web.Http.HttpPost] public HttpResponseMessage Wait(TwilioVoiceRequest request, [FromUri]Guid? callerId = null, [FromUri]int contactId = 0, [FromUri]string redirectTo = null) { try { request.AddAdditionalFields(callerId, contactId, redirectTo: redirectTo); if (Convert.ToInt32(request.QueueTime) == 0) { var history = CallFromTwilioRequest(request); history.ParentID = history.Id; VoipEngine.SaveOrUpdateCall(history); var to = request.RedirectTo; if (string.IsNullOrEmpty(to)) { request.GetSignalRHelper() .Enqueue(request.CallSid, callerId.HasValue ? callerId.Value.ToString() : ""); } else { request.GetSignalRHelper().Incoming(request.CallSid, to); } } return GetHttpResponse(request.Wait()); } catch (Exception e) { Log.Error(e); throw; } } [System.Web.Http.HttpPost] public HttpResponseMessage GatherQueue(TwilioVoiceRequest request, [FromUri]Guid? callerId = null, [FromUri]int contactId = 0) { try { request.AddAdditionalFields(callerId, contactId); return GetHttpResponse(request.GatherQueue()); } catch (Exception e) { Log.Error(e); throw; } } [System.Web.Http.HttpPost] public HttpResponseMessage Redirect(TwilioVoiceRequest request, [FromUri]string redirectTo, [FromUri]Guid? callerId = null) { try { request.AddAdditionalFields(callerId, redirectTo: redirectTo); return GetHttpResponse(request.Redirect()); } catch (Exception e) { Log.Error(e); throw; } } [System.Web.Http.HttpPost] public HttpResponseMessage VoiceMail(TwilioVoiceRequest request, [FromUri]Guid? callerId = null, [FromUri]int contactId = 0) { try { request.AddAdditionalFields(callerId, contactId); MissCall(request, VoipEngine); return GetHttpResponse(request.VoiceMail()); } catch (Exception e) { Log.Error(e); throw; } } private VoiceResponse Inbound(TwilioVoiceRequest request, DaoFactory daoFactory) { SecurityContext.AuthenticateMe(TenantManager.GetCurrentTenant().OwnerId); var call = SaveCall(request, VoipCallStatus.Incoming, daoFactory); return request.Inbound(call, daoFactory); } private VoiceResponse Outbound(TwilioVoiceRequest request, DaoFactory daoFactory) { SaveCall(request, VoipCallStatus.Outcoming, daoFactory); var history = CallFromTwilioRequest(request); history.ParentID = history.Id; VoipEngine.SaveOrUpdateCall(history); return request.Outbound(); } private VoipCall SaveCall(TwilioVoiceRequest request, VoipCallStatus status, DaoFactory daoFactory) { var call = CallFromTwilioRequest(request); call.Status = status; return DaoFactory.GetVoipDao().SaveOrUpdateCall(call); } private void MissCall(TwilioVoiceRequest request, VoipEngine voipEngine) { var voipCall = CallFromTwilioRequest(request); voipCall.Status = VoipCallStatus.Missed; if (!string.IsNullOrEmpty(request.RecordingSid)) { if (voipCall.VoipRecord == null || string.IsNullOrEmpty(voipCall.VoipRecord.Id)) { voipCall.VoipRecord = new VoipRecord { Id = request.RecordingSid }; } } voipCall = voipEngine.SaveOrUpdateCall(voipCall); request.GetSignalRHelper().MissCall(request.CallSid, voipCall.AnsweredBy.ToString()); voipEngine.SaveAdditionalInfo(voipCall.Id); } private VoipCall CallFromTwilioRequest(TwilioVoiceRequest request) { if (!string.IsNullOrEmpty(request.DialCallSid)) { return new VoipCall { Id = request.DialCallSid, ParentID = request.CallSid, From = request.From, To = request.To, AnsweredBy = request.CallerId, EndDialDate = DateTime.UtcNow }; } return new VoipCall { Id = request.CallSid, ParentID = request.ParentCallSid, From = request.From, To = request.To, AnsweredBy = request.CallerId, Date = DateTime.UtcNow, ContactId = request.ContactId }; } private HttpResponseMessage GetHttpResponse(VoiceResponse response) { Log.Info(response); return new HttpResponseMessage { Content = new StringContent(response.ToString(), Encoding.UTF8, "application/xml") }; } } public class TwilioVoiceRequest : VoiceRequest { public TwilioVoiceRequest(TwilioResponseHelper twilioResponseHelper, CommonLinkUtility commonLinkUtility, DaoFactory daoFactory, SecurityContext securityContext, CRMSecurity cRMSecurity, VoipEngine voipEngine, SignalrServiceClient signalrServiceClient) { DaoFactory = daoFactory; CommonLinkUtility = commonLinkUtility; TwilioResponseHelper = twilioResponseHelper; SecurityContext = securityContext; CRMSecurity = cRMSecurity; VoipEngine = voipEngine; SignalrServiceClient = signalrServiceClient; } public SignalrServiceClient SignalrServiceClient { get; } public VoipEngine VoipEngine { get; } public CRMSecurity CRMSecurity { get; } public TwilioResponseHelper TwilioResponseHelper { get; } public CommonLinkUtility CommonLinkUtility { get; } public DaoFactory DaoFactory { get; } public SecurityContext SecurityContext { get; } public Guid CallerId { get; set; } public int ContactId { get; set; } public string ParentCallSid { get; set; } public string QueueResult { get; set; } public string QueueTime { get; set; } public string QueueSid { get; set; } public bool Reject { get; set; } public string RedirectTo { get; set; } public string CurrentQueueSize { get; set; } public bool Pause { get { return GetSettings().Pause; } } private TwilioResponseHelper GetTwilioResponseHelper() { return TwilioResponseHelper; } private VoipSettings settings; private VoipSettings GetSettings() { return settings ?? (settings = DaoFactory.GetVoipDao().GetNumber(IsInbound ? To : From).Settings); } private SignalRHelper signalRHelper; public SignalRHelper GetSignalRHelper() { return signalRHelper ?? (signalRHelper = new SignalRHelper(IsInbound ? To : From, SignalrServiceClient)); } public bool IsInbound { get { return Direction == "inbound"; } } public void AddAdditionalFields(Guid? callerId, int contactId = 0, string reject = null, string redirectTo = null) { if (callerId.HasValue && !callerId.Value.Equals(ASC.Core.Configuration.Constants.Guest.ID)) { CallerId = callerId.Value; SecurityContext.AuthenticateMe(CallerId); } if (contactId != 0) { ContactId = contactId; } if (!string.IsNullOrEmpty(reject)) { Reject = Convert.ToBoolean(reject); } if (!string.IsNullOrEmpty(redirectTo)) { RedirectTo = redirectTo; } } internal VoiceResponse Inbound(VoipCall call, DaoFactory daoFactory) { var contactPhone = call.Status == VoipCallStatus.Incoming || call.Status == VoipCallStatus.Answered ? call.From : call.To; Contact contact; var contacts = VoipEngine.GetContacts(contactPhone, daoFactory); var managers = contacts.SelectMany(CRMSecurity.GetAccessSubjectGuidsTo).ToList(); var agent = GetSignalRHelper().GetAgent(managers); if (agent != null && agent.Item1 != null) { var agentId = agent.Item1.Id; SecurityContext.AuthenticateMe(agentId); call.AnsweredBy = agentId; contact = contacts.FirstOrDefault(CRMSecurity.CanAccessTo); DaoFactory.GetVoipDao().SaveOrUpdateCall(call); } else { contact = contacts.FirstOrDefault(); } if (contact == null) { contact = VoipEngine.CreateContact(call.From.TrimStart('+')); call.ContactId = contact.ID; DaoFactory.GetVoipDao().SaveOrUpdateCall(call); } return GetTwilioResponseHelper().Inbound(agent); } internal VoiceResponse Outbound() { return GetTwilioResponseHelper().Outbound(); } internal VoiceResponse Dial() { return GetTwilioResponseHelper().Dial(); } internal VoiceResponse Enqueue(string queueResult) { return GetTwilioResponseHelper().Enqueue(queueResult); } internal VoiceResponse Queue() { return GetTwilioResponseHelper().Queue(); } internal VoiceResponse Leave() { return GetTwilioResponseHelper().Leave(); } internal VoiceResponse Dequeue() { return GetTwilioResponseHelper().Dequeue(); } internal VoiceResponse Wait() { return GetTwilioResponseHelper().Wait(QueueSid, QueueTime, QueueTime); } internal VoiceResponse GatherQueue() { return GetTwilioResponseHelper().GatherQueue(Digits, To.Substring(1), new List()); } internal VoiceResponse Redirect() { return GetTwilioResponseHelper().Redirect(RedirectTo); } internal VoiceResponse VoiceMail() { return GetTwilioResponseHelper().VoiceMail(); } } public class ValidateRequestFilter : ActionFilterAttribute { public ValidateRequestFilter(ConsumerFactory consumerFactory, IHttpContextAccessor httpContextAccessor) { ConsumerFactory = consumerFactory; HttpContext = httpContextAccessor?.HttpContext; } public HttpContext HttpContext { get; } public ConsumerFactory ConsumerFactory { get; } public override void OnActionExecuting(ActionExecutingContext filterContext) { if (!new RequestValidationHelper().IsValidRequest(filterContext.HttpContext, ConsumerFactory.Get()["twilioAuthToken"], HttpContext.Request.GetUrlRewriter().AbsoluteUri)) filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden); base.OnActionExecuting(filterContext); } } public static class TwilioControllerExtension { public static DIHelper AddTwilioControllerService(this DIHelper services) { services.TryAddScoped(); return services.AddDaoFactoryService() .AddVoipEngineService() .AddTenantManagerService() .AddSecurityContextService(); } } public static class TwilioVoiceRequestExtension { public static DIHelper AddTwilioVoiceRequestService(this DIHelper services) { services.TryAddScoped(); return services.AddCommonLinkUtilityService() .AddDaoFactoryService() .AddSecurityContextService() .AddCRMSecurityService() .AddSignalrServiceClient(); } } public static class ValidateRequestFilterExtension { public static DIHelper AddValidateRequestAttributeService(this DIHelper services) { services.TryAddScoped(); return services.AddConsumerFactoryService(); } } }