using System.Security.Cryptography; using System.Text; using System.Web; using Bit.Core.Settings; using Bit.Core.Utilities; using Microsoft.Azure.NotificationHubs; namespace Bit.Core.NotificationHub; public class NotificationHubConnection { public string HubName { get; init; } public string ConnectionString { get; init; } private Lazy _parsedConnectionString; public Uri Endpoint => _parsedConnectionString.Value.Endpoint; private string SasKey => _parsedConnectionString.Value.SharedAccessKey; private string SasKeyName => _parsedConnectionString.Value.SharedAccessKeyName; public bool EnableSendTracing { get; init; } private NotificationHubClient _hubClient; /// /// Gets the NotificationHubClient for this connection. /// /// If the client is null, it will be initialized. /// /// Exception if the connection is invalid. /// public NotificationHubClient HubClient { get { if (_hubClient == null) { if (!IsValid) { throw new Exception("Invalid notification hub settings"); } Init(); } return _hubClient; } private set { _hubClient = value; } } /// /// Gets the start date for registration. /// /// If null, registration is always disabled. /// public DateTime? RegistrationStartDate { get; init; } /// /// Gets the end date for registration. /// /// If null, registration has no end date. /// public DateTime? RegistrationEndDate { get; init; } /// /// Gets whether all data needed to generate a connection to Notification Hub is present. /// public bool IsValid { get { { var invalid = string.IsNullOrWhiteSpace(HubName) || string.IsNullOrWhiteSpace(ConnectionString); return !invalid; } } } public string LogString { get { return $"HubName: {HubName}, EnableSendTracing: {EnableSendTracing}, RegistrationStartDate: {RegistrationStartDate}, RegistrationEndDate: {RegistrationEndDate}"; } } /// /// Gets whether registration is enabled for the given comb ID. /// This is based off of the generation time encoded in the comb ID. /// /// /// public bool RegistrationEnabled(Guid comb) { var combTime = CoreHelpers.DateFromComb(comb); return RegistrationEnabled(combTime); } /// /// Gets whether registration is enabled for the given time. /// /// The time to check /// public bool RegistrationEnabled(DateTime queryTime) { if (queryTime >= RegistrationEndDate || RegistrationStartDate == null) { return false; } return RegistrationStartDate < queryTime; } public HttpRequestMessage CreateRequest(HttpMethod method, string pathUri, params string[] queryParameters) { var uriBuilder = new UriBuilder(Endpoint) { Scheme = "https", Path = $"{HubName}/{pathUri.TrimStart('/')}", Query = string.Join('&', [.. queryParameters, "api-version=2015-01"]), }; var result = new HttpRequestMessage(method, uriBuilder.Uri); result.Headers.Add("Authorization", GenerateSasToken(uriBuilder.Uri)); result.Headers.Add("TrackingId", Guid.NewGuid().ToString()); return result; } private string GenerateSasToken(Uri uri) { string targetUri = Uri.EscapeDataString(uri.ToString().ToLower()).ToLower(); long expires = DateTime.UtcNow.AddMinutes(1).Ticks / TimeSpan.TicksPerSecond; string stringToSign = targetUri + "\n" + expires; using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(SasKey))) { var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign))); return $"SharedAccessSignature sr={targetUri}&sig={HttpUtility.UrlEncode(signature)}&se={expires}&skn={SasKeyName}"; } } private NotificationHubConnection() { _parsedConnectionString = new(() => new NotificationHubConnectionStringBuilder(ConnectionString)); } /// /// Creates a new NotificationHubConnection from the given settings. /// /// /// public static NotificationHubConnection From(GlobalSettings.NotificationHubSettings settings) { return new() { HubName = settings.HubName, ConnectionString = settings.ConnectionString, EnableSendTracing = settings.EnableSendTracing, // Comb time is not precise enough for millisecond accuracy RegistrationStartDate = settings.RegistrationStartDate.HasValue ? Truncate(settings.RegistrationStartDate.Value, TimeSpan.FromMilliseconds(10)) : null, RegistrationEndDate = settings.RegistrationEndDate }; } private NotificationHubConnection Init() { HubClient = NotificationHubClient.CreateClientFromConnectionString(ConnectionString, HubName, EnableSendTracing); return this; } private static DateTime Truncate(DateTime dateTime, TimeSpan resolution) { return dateTime.AddTicks(-(dateTime.Ticks % resolution.Ticks)); } }