DocSpace-buildtools/web/ASC.Web.Core/Sms/SmsProvider.cs

559 lines
20 KiB
C#
Raw Normal View History

2019-06-07 08:59:07 +00:00
/*
*
* (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 System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
2021-10-12 10:14:33 +00:00
using System.Net.Http;
2019-06-07 08:59:07 +00:00
using System.Text.RegularExpressions;
using System.Web;
2019-12-11 14:30:28 +00:00
2020-02-17 08:58:14 +00:00
using ASC.Common;
2019-06-07 08:59:07 +00:00
using ASC.Common.Caching;
using ASC.Common.Logging;
using ASC.Core;
2019-09-19 15:55:44 +00:00
using ASC.Core.Common;
2019-06-07 08:59:07 +00:00
using ASC.Core.Common.Configuration;
using ASC.Core.Tenants;
using ASC.FederatedLogin.LoginProviders;
using ASC.VoipService.Dao;
2019-12-11 14:30:28 +00:00
2019-09-27 11:51:09 +00:00
using Microsoft.Extensions.Configuration;
2019-10-17 15:55:35 +00:00
using Microsoft.Extensions.Options;
2019-12-11 14:30:28 +00:00
2019-06-07 08:59:07 +00:00
using Twilio.Clients;
using Twilio.Rest.Api.V2010.Account;
using Twilio.Types;
namespace ASC.Web.Core.Sms
{
2020-10-28 20:02:03 +00:00
[Scope(Additional = typeof(TwilioProviderExtention))]
2019-12-20 11:17:01 +00:00
public class SmsProviderManager
2019-06-07 08:59:07 +00:00
{
2019-12-20 11:17:01 +00:00
public SmscProvider SmscProvider { get => ConsumerFactory.Get<SmscProvider>(); }
2020-08-12 09:58:08 +00:00
private ConsumerFactory ConsumerFactory { get; }
2019-06-07 08:59:07 +00:00
2019-12-20 11:17:01 +00:00
public ClickatellProvider ClickatellProvider { get => ConsumerFactory.Get<ClickatellProvider>(); }
public TwilioProvider TwilioProvider { get => ConsumerFactory.Get<TwilioProvider>(); }
2019-06-07 08:59:07 +00:00
2019-12-20 11:17:01 +00:00
public ClickatellProvider ClickatellUSAProvider { get => ConsumerFactory.Get<ClickatellUSAProvider>(); }
public TwilioProvider TwilioSaaSProvider { get => ConsumerFactory.Get<TwilioSaaSProvider>(); }
2019-06-07 08:59:07 +00:00
2019-12-20 11:17:01 +00:00
public SmsProviderManager(ConsumerFactory consumerFactory)
{
ConsumerFactory = consumerFactory;
2019-06-07 08:59:07 +00:00
}
2019-12-20 11:17:01 +00:00
public bool Enabled()
2019-06-07 08:59:07 +00:00
{
return SmscProvider.Enable() || ClickatellProvider.Enable() || ClickatellUSAProvider.Enable() || TwilioProvider.Enable() || TwilioSaaSProvider.Enable();
}
2019-12-20 11:17:01 +00:00
public bool SendMessage(string number, string message)
2019-06-07 08:59:07 +00:00
{
if (!Enabled()) return false;
SmsProvider provider = null;
if (ClickatellProvider.Enable())
{
provider = ClickatellProvider;
}
string smsUsa;
if (ClickatellUSAProvider.Enable()
&& !string.IsNullOrEmpty(smsUsa = ClickatellProvider["clickatellUSA"]) && Regex.IsMatch(number, smsUsa))
{
provider = ClickatellUSAProvider;
}
if (provider == null && TwilioProvider.Enable())
{
provider = TwilioProvider;
}
if (provider == null && TwilioSaaSProvider.Enable())
{
provider = TwilioSaaSProvider;
}
if (SmscProvider.Enable()
&& (provider == null
|| SmscProvider.SuitableNumber(number)))
{
provider = SmscProvider;
}
if (provider == null)
{
return false;
}
return provider.SendMessage(number, message);
}
}
public abstract class SmsProvider : Consumer
{
2022-01-13 11:19:39 +00:00
protected ILog Log { get; }
protected IHttpClientFactory ClientFactory { get; }
2021-01-12 17:51:14 +00:00
protected ICache MemoryCache { get; set; }
2019-06-07 08:59:07 +00:00
protected virtual string SendMessageUrlFormat { get; set; }
protected virtual string GetBalanceUrlFormat { get; set; }
protected virtual string Key { get; set; }
protected virtual string Secret { get; set; }
protected virtual string Sender { get; set; }
protected SmsProvider()
{
}
2019-10-11 15:03:03 +00:00
protected SmsProvider(
TenantManager tenantManager,
CoreBaseSettings coreBaseSettings,
CoreSettings coreSettings,
IConfiguration configuration,
ICacheNotify<ConsumerCacheItem> cache,
ConsumerFactory consumerFactory,
2019-11-06 15:03:09 +00:00
IOptionsMonitor<ILog> options,
2022-01-13 11:19:39 +00:00
IHttpClientFactory clientFactory,
2021-01-12 17:51:14 +00:00
ICache memCache,
2019-09-27 11:51:09 +00:00
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, name, order, props, additional)
2019-06-07 08:59:07 +00:00
{
2021-01-12 17:51:14 +00:00
MemoryCache = memCache;
2019-11-06 15:03:09 +00:00
Log = options.CurrentValue;
2022-01-13 11:19:39 +00:00
ClientFactory = clientFactory;
2019-06-07 08:59:07 +00:00
}
public virtual bool Enable()
{
return true;
}
private string SendMessageUrl()
{
return SendMessageUrlFormat
.Replace("{key}", Key)
.Replace("{secret}", Secret)
.Replace("{sender}", Sender);
}
public virtual bool SendMessage(string number, string message)
{
try
{
var url = SendMessageUrl();
url = url.Replace("{phone}", number).Replace("{text}", HttpUtility.UrlEncode(message));
2021-10-12 10:14:33 +00:00
var request = new HttpRequestMessage();
request.RequestUri = new Uri(url);
request.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
2021-10-21 14:56:31 +00:00
2022-01-13 11:19:39 +00:00
var httpClient = ClientFactory.CreateClient();
2021-10-12 10:14:33 +00:00
httpClient.Timeout = TimeSpan.FromMilliseconds(15000);
2019-06-07 08:59:07 +00:00
2021-11-24 19:34:39 +00:00
using var response = httpClient.Send(request);
2021-10-12 10:14:33 +00:00
using var stream = response.Content.ReadAsStream();
2019-08-15 15:08:40 +00:00
if (stream != null)
2019-06-07 08:59:07 +00:00
{
2019-08-15 15:08:40 +00:00
using var reader = new StreamReader(stream);
var result = reader.ReadToEnd();
Log.InfoFormat("SMS was sent to {0}, service returned: {1}", number, result);
return true;
2019-06-07 08:59:07 +00:00
}
}
catch (Exception ex)
{
Log.Error("Failed to send sms message", ex);
}
return false;
}
}
public class SmscProvider : SmsProvider, IValidateKeysProvider
{
public SmscProvider()
{
}
2019-10-11 15:03:03 +00:00
public SmscProvider(
TenantManager tenantManager,
CoreBaseSettings coreBaseSettings,
CoreSettings coreSettings,
IConfiguration configuration,
ICacheNotify<ConsumerCacheItem> cache,
ConsumerFactory consumerFactory,
2019-11-06 15:03:09 +00:00
IOptionsMonitor<ILog> options,
2022-01-13 11:19:39 +00:00
IHttpClientFactory clientFactory,
2021-01-12 17:51:14 +00:00
ICache memCache,
2019-10-11 15:03:03 +00:00
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
2022-01-13 11:19:39 +00:00
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, options, clientFactory, memCache, name, order, props, additional)
2019-06-07 08:59:07 +00:00
{
}
protected override string SendMessageUrlFormat
{
get { return "https://smsc.ru/sys/send.php?login={key}&psw={secret}&phones={phone}&mes={text}&fmt=3&sender={sender}&charset=utf-8"; }
set { }
}
protected override string GetBalanceUrlFormat
{
get { return "https://smsc.ru/sys/balance.php?login={key}&psw={secret}"; }
set { }
}
protected override string Key
{
get { return this["smsclogin"]; }
}
protected override string Secret
{
get { return this["smscpsw"]; }
}
protected override string Sender
{
get { return this["smscsender"]; }
}
public override bool Enable()
{
return
!string.IsNullOrEmpty(Key)
&& !string.IsNullOrEmpty(Secret);
}
2019-09-20 12:47:00 +00:00
public string GetBalance(Tenant tenant, bool eraseCache = false)
2019-06-07 08:59:07 +00:00
{
var tenantCache = tenant == null ? Tenant.DEFAULT_TENANT : tenant.TenantId;
var key = "sms/smsc/" + tenantCache;
2020-10-12 13:52:31 +00:00
if (eraseCache) MemoryCache.Remove(key);
2019-06-07 08:59:07 +00:00
2020-10-12 13:52:31 +00:00
var balance = MemoryCache.Get<string>(key);
2019-06-07 08:59:07 +00:00
if (string.IsNullOrEmpty(balance))
{
try
{
var url = GetBalanceUrl();
2021-10-12 10:14:33 +00:00
var request = new HttpRequestMessage();
request.RequestUri = new Uri(url);
request.Headers.Add("Content-Type", "application/x-www-form-urlencoded");
2019-06-07 08:59:07 +00:00
2022-01-13 11:19:39 +00:00
var httpClient = ClientFactory.CreateClient();
2021-10-12 10:14:33 +00:00
httpClient.Timeout = TimeSpan.FromMilliseconds(1000);
2021-11-24 19:34:39 +00:00
using var response = httpClient.Send(request);
2021-10-12 10:14:33 +00:00
using var stream = response.Content.ReadAsStream();
2019-08-15 15:08:40 +00:00
if (stream != null)
2019-06-07 08:59:07 +00:00
{
2019-08-15 15:08:40 +00:00
using var reader = new StreamReader(stream);
var result = reader.ReadToEnd();
Log.InfoFormat("SMS balance service returned: {0}", result);
balance = result;
2019-06-07 08:59:07 +00:00
}
}
catch (Exception ex)
{
Log.Error("Failed request sms balance", ex);
balance = string.Empty;
}
2020-10-12 13:52:31 +00:00
MemoryCache.Insert(key, balance, TimeSpan.FromMinutes(1));
2019-06-07 08:59:07 +00:00
}
return balance;
}
private string GetBalanceUrl()
{
return GetBalanceUrlFormat
.Replace("{key}", Key)
.Replace("{secret}", Secret);
}
public bool SuitableNumber(string number)
{
var smsCis = this["smsccis"];
return !string.IsNullOrEmpty(smsCis) && Regex.IsMatch(number, smsCis);
}
2020-08-26 14:34:21 +00:00
public bool ValidateKeys()
2019-06-07 08:59:07 +00:00
{
2020-08-26 14:34:21 +00:00
return double.TryParse(GetBalance(TenantManager.GetCurrentTenant(false), true), NumberStyles.Number, CultureInfo.InvariantCulture, out var balance) && balance > 0;
2019-06-07 08:59:07 +00:00
}
}
public class ClickatellProvider : SmsProvider
{
protected override string SendMessageUrlFormat
{
get { return "https://platform.clickatell.com/messages/http/send?apiKey={secret}&to={phone}&content={text}&from={sender}"; }
set { }
}
protected override string Secret
{
get { return this["clickatellapiKey"]; }
set { }
}
protected override string Sender
{
get { return this["clickatellSender"]; }
set { }
}
public override bool Enable()
{
return !string.IsNullOrEmpty(Secret);
}
public ClickatellProvider()
{
}
2019-10-11 15:03:03 +00:00
public ClickatellProvider(
TenantManager tenantManager,
CoreBaseSettings coreBaseSettings,
CoreSettings coreSettings,
IConfiguration configuration,
ICacheNotify<ConsumerCacheItem> cache,
ConsumerFactory consumerFactory,
2019-11-06 15:03:09 +00:00
IOptionsMonitor<ILog> options,
2022-01-13 11:19:39 +00:00
IHttpClientFactory clientFactory,
2021-01-12 17:51:14 +00:00
ICache memCache,
2019-09-27 11:51:09 +00:00
string name, int order, Dictionary<string, string> props, Dictionary<string, string> additional = null)
2022-01-13 11:19:39 +00:00
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, options, clientFactory, memCache, name, order, props, additional)
2019-06-07 08:59:07 +00:00
{
}
}
public class ClickatellUSAProvider : ClickatellProvider
{
public ClickatellUSAProvider()
{
}
2019-10-11 15:03:03 +00:00
public ClickatellUSAProvider(
TenantManager tenantManager,
CoreBaseSettings coreBaseSettings,
CoreSettings coreSettings,
IConfiguration configuration,
ICacheNotify<ConsumerCacheItem> cache,
ConsumerFactory consumerFactory,
2019-11-06 15:03:09 +00:00
IOptionsMonitor<ILog> options,
2022-01-13 11:19:39 +00:00
IHttpClientFactory clientFactory,
2021-01-12 17:51:14 +00:00
ICache memCache,
2019-09-27 11:51:09 +00:00
string name, int order, Dictionary<string, string> additional = null)
2022-01-13 11:19:39 +00:00
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, options, clientFactory, memCache, name, order, null, additional)
2019-06-07 08:59:07 +00:00
{
}
}
2020-10-19 15:53:15 +00:00
[Scope]
2019-06-07 08:59:07 +00:00
public class TwilioProvider : SmsProvider, IValidateKeysProvider
{
protected override string Key
{
get { return this["twilioAccountSid"]; }
set { }
}
protected override string Secret
{
get { return this["twilioAuthToken"]; }
set { }
}
protected override string Sender
{
get { return this["twiliosender"]; }
set { }
}
2020-08-26 14:34:21 +00:00
public AuthContext AuthContext { get; }
public TenantUtil TenantUtil { get; }
public SecurityContext SecurityContext { get; }
public BaseCommonLinkUtility BaseCommonLinkUtility { get; }
public TwilioProviderCleaner TwilioProviderCleaner { get; }
2020-04-17 14:07:44 +00:00
2019-06-07 08:59:07 +00:00
public override bool Enable()
{
return
!string.IsNullOrEmpty(Key)
&& !string.IsNullOrEmpty(Secret)
&& !string.IsNullOrEmpty(Sender);
}
public override bool SendMessage(string number, string message)
{
if (!number.StartsWith("+")) number = "+" + number;
var twilioRestClient = new TwilioRestClient(Key, Secret);
try
{
var smsMessage = MessageResource.Create(new PhoneNumber(number), body: message, @from: new PhoneNumber(Sender), client: twilioRestClient);
Log.InfoFormat("SMS was sent to {0}, status: {1}", number, smsMessage.Status);
if (!smsMessage.ErrorCode.HasValue)
{
return true;
}
Log.Error("Failed to send sms. code: " + smsMessage.ErrorCode.Value + " message: " + smsMessage.ErrorMessage);
}
catch (Exception ex)
{
Log.Error("Failed to send sms message via tiwilio", ex);
}
return false;
}
public TwilioProvider()
{
}
2019-10-11 15:03:03 +00:00
public TwilioProvider(
2020-08-26 14:34:21 +00:00
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility,
TwilioProviderCleaner twilioProviderCleaner,
2019-10-11 15:03:03 +00:00
TenantManager tenantManager,
CoreBaseSettings coreBaseSettings,
CoreSettings coreSettings,
IConfiguration configuration,
ICacheNotify<ConsumerCacheItem> cache,
ConsumerFactory consumerFactory,
2019-11-06 15:03:09 +00:00
IOptionsMonitor<ILog> options,
2022-01-13 11:19:39 +00:00
IHttpClientFactory clientFactory,
2021-01-12 17:51:14 +00:00
ICache memCache,
2020-08-26 14:34:21 +00:00
string name, int order, Dictionary<string, string> props)
2022-01-13 11:19:39 +00:00
: base(tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, options, clientFactory, memCache, name, order, props)
2019-06-07 08:59:07 +00:00
{
2020-08-26 14:34:21 +00:00
AuthContext = authContext;
TenantUtil = tenantUtil;
SecurityContext = securityContext;
BaseCommonLinkUtility = baseCommonLinkUtility;
TwilioProviderCleaner = twilioProviderCleaner;
2019-06-07 08:59:07 +00:00
}
2020-08-26 14:34:21 +00:00
public bool ValidateKeys()
2019-06-07 08:59:07 +00:00
{
try
{
2020-10-12 19:39:23 +00:00
new VoipService.Twilio.TwilioProvider(Key, Secret, AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility).GetExistingPhoneNumbers();
2019-06-07 08:59:07 +00:00
return true;
}
catch (Exception)
{
return false;
}
}
2020-08-26 14:34:21 +00:00
public void ClearOldNumbers()
2019-06-07 08:59:07 +00:00
{
2020-08-26 14:34:21 +00:00
TwilioProviderCleaner.ClearOldNumbers(Key, Secret);
2019-06-07 08:59:07 +00:00
}
}
2020-10-19 15:53:15 +00:00
[Scope]
2019-06-07 08:59:07 +00:00
public class TwilioSaaSProvider : TwilioProvider
{
public TwilioSaaSProvider()
{
}
2019-10-11 15:03:03 +00:00
public TwilioSaaSProvider(
2020-08-26 14:34:21 +00:00
AuthContext authContext,
TenantUtil tenantUtil,
SecurityContext securityContext,
BaseCommonLinkUtility baseCommonLinkUtility,
TwilioProviderCleaner twilioProviderCleaner,
2019-10-11 15:03:03 +00:00
TenantManager tenantManager,
CoreBaseSettings coreBaseSettings,
CoreSettings coreSettings,
IConfiguration configuration,
ICacheNotify<ConsumerCacheItem> cache,
ConsumerFactory consumerFactory,
2019-11-06 15:03:09 +00:00
IOptionsMonitor<ILog> options,
2022-01-13 11:19:39 +00:00
IHttpClientFactory clientFactory,
2021-01-12 17:51:14 +00:00
ICache memCache,
2020-08-26 14:34:21 +00:00
string name, int order)
2022-01-13 11:19:39 +00:00
: base(authContext, tenantUtil, securityContext, baseCommonLinkUtility, twilioProviderCleaner, tenantManager, coreBaseSettings, coreSettings, configuration, cache, consumerFactory, options, clientFactory, memCache, name, order, null)
2020-08-26 14:34:21 +00:00
{
}
}
2020-10-19 15:53:15 +00:00
[Scope]
2020-08-26 14:34:21 +00:00
public class TwilioProviderCleaner
{
private VoipDao VoipDao { get; }
private AuthContext AuthContext { get; }
private TenantUtil TenantUtil { get; }
private SecurityContext SecurityContext { get; }
private BaseCommonLinkUtility BaseCommonLinkUtility { get; }
2020-10-12 13:52:31 +00:00
public TwilioProviderCleaner(VoipDao voipDao, AuthContext authContext, TenantUtil tenantUtil, SecurityContext securityContext, BaseCommonLinkUtility baseCommonLinkUtility)
2020-08-26 14:34:21 +00:00
{
VoipDao = voipDao;
AuthContext = authContext;
TenantUtil = tenantUtil;
SecurityContext = securityContext;
BaseCommonLinkUtility = baseCommonLinkUtility;
}
public void ClearOldNumbers(string key, string secret)
2019-06-07 08:59:07 +00:00
{
2020-08-26 14:34:21 +00:00
if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(secret)) return;
var provider = new VoipService.Twilio.TwilioProvider(key, secret, AuthContext, TenantUtil, SecurityContext, BaseCommonLinkUtility);
var numbers = VoipDao.GetNumbers();
foreach (var number in numbers)
{
provider.DisablePhone(number);
VoipDao.DeleteNumber(number.Id);
}
2019-06-07 08:59:07 +00:00
}
}
2020-04-17 14:07:44 +00:00
public static class TwilioProviderExtention
{
2020-10-28 20:02:03 +00:00
public static void Register(DIHelper services)
2020-04-17 14:07:44 +00:00
{
2020-10-28 20:02:03 +00:00
services.TryAdd<TwilioSaaSProvider>();
2020-04-17 14:07:44 +00:00
}
}
2019-06-07 08:59:07 +00:00
}