2022-06-29 19:46:41 -04:00
using System.Text.Json ;
2021-11-09 11:25:18 -05:00
using System.Text.RegularExpressions ;
2021-02-04 12:54:21 -06:00
using Bit.Core.Context ;
2022-01-11 10:40:51 +01:00
using Bit.Core.Entities ;
2017-05-26 22:52:50 -04:00
using Bit.Core.Enums ;
2017-05-30 00:02:20 -04:00
using Bit.Core.Models ;
2019-03-19 00:39:03 -04:00
using Bit.Core.Models.Data ;
using Bit.Core.Repositories ;
2021-02-22 15:35:16 -06:00
using Bit.Core.Settings ;
2023-03-02 13:23:38 -05:00
using Bit.Core.Vault.Entities ;
2021-11-09 11:25:18 -05:00
using Microsoft.AspNetCore.Http ;
using Microsoft.Azure.NotificationHubs ;
2022-10-19 10:22:40 -04:00
using Microsoft.Extensions.Logging ;
2017-05-26 22:52:50 -04:00
namespace Bit.Core.Services ;
2022-08-29 16:06:55 -04:00
2017-05-26 22:52:50 -04:00
public class NotificationHubPushNotificationService : IPushNotificationService
{
private readonly IInstallationDeviceRepository _installationDeviceRepository ;
private readonly GlobalSettings _globalSettings ;
private readonly IHttpContextAccessor _httpContextAccessor ;
private NotificationHubClient _client = null ;
2022-10-19 10:22:40 -04:00
private ILogger _logger ;
2022-08-29 16:06:55 -04:00
2017-05-26 22:52:50 -04:00
public NotificationHubPushNotificationService (
IInstallationDeviceRepository installationDeviceRepository ,
GlobalSettings globalSettings ,
2022-10-19 10:22:40 -04:00
IHttpContextAccessor httpContextAccessor ,
ILogger < NotificationsApiPushNotificationService > logger )
2017-05-26 22:52:50 -04:00
{
2019-03-19 00:39:03 -04:00
_installationDeviceRepository = installationDeviceRepository ;
2018-08-06 09:04:31 -04:00
_globalSettings = globalSettings ;
2017-05-26 22:52:50 -04:00
_httpContextAccessor = httpContextAccessor ;
_client = NotificationHubClient . CreateClientFromConnectionString (
2019-02-26 08:15:56 -05:00
_globalSettings . NotificationHub . ConnectionString ,
2022-10-19 10:22:40 -04:00
_globalSettings . NotificationHub . HubName ,
_globalSettings . NotificationHub . EnableSendTracing ) ;
_logger = logger ;
2022-08-29 16:06:55 -04:00
}
2017-05-26 22:52:50 -04:00
2018-08-06 09:04:31 -04:00
public async Task PushSyncCipherCreateAsync ( Cipher cipher , IEnumerable < Guid > collectionIds )
2022-08-29 16:06:55 -04:00
{
2018-08-06 09:04:31 -04:00
await PushCipherAsync ( cipher , PushType . SyncCipherCreate , collectionIds ) ;
2022-08-29 16:06:55 -04:00
}
2018-08-06 09:04:31 -04:00
2017-05-26 22:52:50 -04:00
public async Task PushSyncCipherUpdateAsync ( Cipher cipher , IEnumerable < Guid > collectionIds )
{
2019-02-26 08:15:56 -05:00
await PushCipherAsync ( cipher , PushType . SyncCipherUpdate , collectionIds ) ;
2017-05-26 22:52:50 -04:00
}
2018-08-21 09:29:38 -04:00
public async Task PushSyncCipherDeleteAsync ( Cipher cipher )
2017-05-26 22:52:50 -04:00
{
2018-08-21 09:29:38 -04:00
await PushCipherAsync ( cipher , PushType . SyncLoginDelete , null ) ;
2017-05-26 22:52:50 -04:00
}
2018-08-21 09:29:38 -04:00
private async Task PushCipherAsync ( Cipher cipher , PushType type , IEnumerable < Guid > collectionIds )
2022-08-29 16:06:55 -04:00
{
2020-03-27 14:36:37 -04:00
if ( cipher . OrganizationId . HasValue )
2017-05-26 22:52:50 -04:00
{
2018-08-21 09:29:38 -04:00
// We cannot send org pushes since access logic is much more complicated than just the fact that they belong
// to the organization. Potentially we could blindly send to just users that have the access all permission
// device registration needs to be more granular to handle that appropriately. A more brute force approach could
// me to send "full sync" push to all org users, but that has the potential to DDOS the API in bursts.
2017-05-26 22:52:50 -04:00
2018-08-21 09:29:38 -04:00
// await SendPayloadToOrganizationAsync(cipher.OrganizationId.Value, type, message, true);
2017-05-26 22:52:50 -04:00
}
2018-08-21 09:29:38 -04:00
else if ( cipher . UserId . HasValue )
2017-05-26 22:52:50 -04:00
{
2020-03-27 14:36:37 -04:00
var message = new SyncCipherPushNotification
2017-05-26 22:52:50 -04:00
{
Id = cipher . Id ,
UserId = cipher . UserId ,
OrganizationId = cipher . OrganizationId ,
RevisionDate = cipher . RevisionDate ,
2022-06-13 13:50:44 -04:00
CollectionIds = collectionIds ,
2017-05-26 22:52:50 -04:00
} ;
2018-08-28 08:22:49 -04:00
await SendPayloadToUserAsync ( cipher . UserId . Value , type , message , true ) ;
2017-05-26 22:52:50 -04:00
}
2022-08-29 16:06:55 -04:00
}
2017-05-26 22:52:50 -04:00
public async Task PushSyncFolderCreateAsync ( Folder folder )
{
2018-08-28 08:22:49 -04:00
await PushFolderAsync ( folder , PushType . SyncFolderCreate ) ;
2017-05-26 22:52:50 -04:00
}
public async Task PushSyncFolderUpdateAsync ( Folder folder )
{
2018-08-28 08:22:49 -04:00
await PushFolderAsync ( folder , PushType . SyncFolderUpdate ) ;
2017-05-26 22:52:50 -04:00
}
public async Task PushSyncFolderDeleteAsync ( Folder folder )
2022-08-29 16:06:55 -04:00
{
2019-03-19 00:39:03 -04:00
await PushFolderAsync ( folder , PushType . SyncFolderDelete ) ;
2022-08-29 16:06:55 -04:00
}
2017-05-26 22:52:50 -04:00
private async Task PushFolderAsync ( Folder folder , PushType type )
2022-08-29 16:06:55 -04:00
{
2018-08-28 08:22:49 -04:00
var message = new SyncFolderPushNotification
2017-05-26 22:52:50 -04:00
{
Id = folder . Id ,
2018-08-28 08:22:49 -04:00
UserId = folder . UserId ,
2017-05-26 22:52:50 -04:00
RevisionDate = folder . RevisionDate
} ;
2018-08-28 08:22:49 -04:00
await SendPayloadToUserAsync ( folder . UserId , type , message , true ) ;
}
public async Task PushSyncCiphersAsync ( Guid userId )
{
2017-05-26 22:52:50 -04:00
await PushUserAsync ( userId , PushType . SyncCiphers ) ;
}
public async Task PushSyncVaultAsync ( Guid userId )
2022-08-29 15:53:48 -04:00
{
2017-05-26 22:52:50 -04:00
await PushUserAsync ( userId , PushType . SyncVault ) ;
}
2021-01-22 16:16:40 -05:00
public async Task PushSyncOrgKeysAsync ( Guid userId )
{
await PushUserAsync ( userId , PushType . SyncOrgKeys ) ;
}
public async Task PushSyncSettingsAsync ( Guid userId )
{
await PushUserAsync ( userId , PushType . SyncSettings ) ;
}
2022-11-24 11:25:16 -05:00
public async Task PushLogOutAsync ( Guid userId , bool excludeCurrentContext = false )
2021-01-22 16:16:40 -05:00
{
2022-11-24 11:25:16 -05:00
await PushUserAsync ( userId , PushType . LogOut , excludeCurrentContext ) ;
2021-01-22 16:16:40 -05:00
}
2022-11-24 11:25:16 -05:00
private async Task PushUserAsync ( Guid userId , PushType type , bool excludeCurrentContext = false )
2022-08-29 16:06:55 -04:00
{
2021-01-22 16:16:40 -05:00
var message = new UserPushNotification
{
UserId = userId ,
Date = DateTime . UtcNow
} ;
2022-11-24 11:25:16 -05:00
await SendPayloadToUserAsync ( userId , type , message , excludeCurrentContext ) ;
2017-05-26 22:52:50 -04:00
}
public async Task PushSyncSendCreateAsync ( Send send )
{
2017-08-11 10:04:59 -04:00
await PushSendAsync ( send , PushType . SyncSendCreate ) ;
}
2019-03-19 00:39:03 -04:00
public async Task PushSyncSendUpdateAsync ( Send send )
2017-08-11 10:04:59 -04:00
{
await PushSendAsync ( send , PushType . SyncSendUpdate ) ;
2019-03-19 00:39:03 -04:00
}
2017-08-11 10:04:59 -04:00
2019-03-19 00:39:03 -04:00
public async Task PushSyncSendDeleteAsync ( Send send )
2017-08-11 10:04:59 -04:00
{
2019-03-19 00:39:03 -04:00
await PushSendAsync ( send , PushType . SyncSendDelete ) ;
2017-05-26 22:52:50 -04:00
}
2017-08-11 10:04:59 -04:00
private async Task PushSendAsync ( Send send , PushType type )
2022-08-29 16:06:55 -04:00
{
2021-02-04 12:54:21 -06:00
if ( send . UserId . HasValue )
2017-05-26 22:52:50 -04:00
{
2021-02-04 12:54:21 -06:00
var message = new SyncSendPushNotification
2022-08-29 15:53:48 -04:00
{
2021-01-22 16:16:40 -05:00
Id = send . Id ,
2021-02-04 12:54:21 -06:00
UserId = send . UserId . Value ,
RevisionDate = send . RevisionDate
2022-08-29 15:53:48 -04:00
} ;
2017-08-11 10:04:59 -04:00
2017-05-26 22:52:50 -04:00
await SendPayloadToUserAsync ( message . UserId , type , message , true ) ;
}
2022-08-29 16:06:55 -04:00
}
2017-05-26 22:52:50 -04:00
2022-09-26 13:21:13 -04:00
public async Task PushAuthRequestAsync ( AuthRequest authRequest )
{
await PushAuthRequestAsync ( authRequest , PushType . AuthRequest ) ;
}
public async Task PushAuthRequestResponseAsync ( AuthRequest authRequest )
{
await PushAuthRequestAsync ( authRequest , PushType . AuthRequestResponse ) ;
}
private async Task PushAuthRequestAsync ( AuthRequest authRequest , PushType type )
{
var message = new AuthRequestPushNotification
{
Id = authRequest . Id ,
UserId = authRequest . UserId
} ;
await SendPayloadToUserAsync ( authRequest . UserId , type , message , true ) ;
}
2017-05-26 22:52:50 -04:00
private async Task SendPayloadToUserAsync ( Guid userId , PushType type , object payload , bool excludeCurrentContext )
2022-08-29 15:53:48 -04:00
{
2017-05-26 22:52:50 -04:00
await SendPayloadToUserAsync ( userId . ToString ( ) , type , payload , GetContextIdentifier ( excludeCurrentContext ) ) ;
2022-01-21 09:36:25 -05:00
}
2021-11-09 11:25:18 -05:00
private async Task SendPayloadToOrganizationAsync ( Guid orgId , PushType type , object payload , bool excludeCurrentContext )
{
2017-05-26 22:52:50 -04:00
await SendPayloadToUserAsync ( orgId . ToString ( ) , type , payload , GetContextIdentifier ( excludeCurrentContext ) ) ;
2022-08-29 15:53:48 -04:00
}
2022-08-29 14:53:16 -04:00
2017-08-11 10:04:59 -04:00
public async Task SendPayloadToUserAsync ( string userId , PushType type , object payload , string identifier ,
string deviceId = null )
2022-08-29 16:06:55 -04:00
{
2021-11-09 11:25:18 -05:00
var tag = BuildTag ( $"template:payload_userId:{SanitizeTagInput(userId)}" , identifier ) ;
2017-05-26 22:52:50 -04:00
await SendPayloadAsync ( tag , type , payload ) ;
2021-11-09 11:25:18 -05:00
if ( InstallationDeviceEntity . IsInstallationDeviceId ( deviceId ) )
2022-08-29 15:53:48 -04:00
{
2017-05-26 22:52:50 -04:00
await _installationDeviceRepository . UpsertAsync ( new InstallationDeviceEntity ( deviceId ) ) ;
2022-08-29 15:53:48 -04:00
}
2022-08-29 16:06:55 -04:00
}
2022-08-29 14:53:16 -04:00
2017-05-26 22:52:50 -04:00
public async Task SendPayloadToOrganizationAsync ( string orgId , PushType type , object payload , string identifier ,
string deviceId = null )
2022-08-29 16:06:55 -04:00
{
2017-05-26 22:52:50 -04:00
var tag = BuildTag ( $"template:payload && organizationId:{SanitizeTagInput(orgId)}" , identifier ) ;
await SendPayloadAsync ( tag , type , payload ) ;
if ( InstallationDeviceEntity . IsInstallationDeviceId ( deviceId ) )
2022-08-29 14:53:16 -04:00
{
2017-05-26 22:52:50 -04:00
await _installationDeviceRepository . UpsertAsync ( new InstallationDeviceEntity ( deviceId ) ) ;
2022-08-29 15:53:48 -04:00
}
2022-08-29 16:06:55 -04:00
}
2022-08-29 15:53:48 -04:00
2021-11-09 11:25:18 -05:00
private string GetContextIdentifier ( bool excludeCurrentContext )
2022-08-29 16:06:55 -04:00
{
2021-11-09 11:25:18 -05:00
if ( ! excludeCurrentContext )
2022-08-29 15:53:48 -04:00
{
2017-05-26 22:52:50 -04:00
return null ;
2022-08-29 15:53:48 -04:00
}
2017-05-26 22:52:50 -04:00
var currentContext = _httpContextAccessor ? . HttpContext ? .
RequestServices . GetService ( typeof ( ICurrentContext ) ) as ICurrentContext ;
2019-02-26 08:15:56 -05:00
return currentContext ? . DeviceIdentifier ;
2022-01-21 09:36:25 -05:00
}
2022-08-29 15:53:48 -04:00
2021-11-09 11:25:18 -05:00
private string BuildTag ( string tag , string identifier )
2022-08-29 16:06:55 -04:00
{
2021-11-09 11:25:18 -05:00
if ( ! string . IsNullOrWhiteSpace ( identifier ) )
2022-08-29 14:53:16 -04:00
{
2021-11-09 11:25:18 -05:00
tag + = $" && !deviceIdentifier:{SanitizeTagInput(identifier)}" ;
2022-08-29 15:53:48 -04:00
}
2022-08-29 16:06:55 -04:00
2017-05-26 22:52:50 -04:00
return $"({tag})" ;
2022-08-29 16:06:55 -04:00
}
2017-08-11 10:04:59 -04:00
private async Task SendPayloadAsync ( string tag , PushType type , object payload )
2022-08-29 16:06:55 -04:00
{
2022-10-19 10:22:40 -04:00
var outcome = await _client . SendTemplateNotificationAsync (
2017-05-26 22:52:50 -04:00
new Dictionary < string , string >
2022-08-29 16:06:55 -04:00
{
2017-05-26 22:52:50 -04:00
{ "type" , ( ( byte ) type ) . ToString ( ) } ,
2022-01-21 09:36:25 -05:00
{ "payload" , JsonSerializer . Serialize ( payload ) }
2022-08-29 16:06:55 -04:00
} , tag ) ;
2022-10-19 10:22:40 -04:00
if ( _globalSettings . NotificationHub . EnableSendTracing )
{
_logger . LogInformation ( "Azure Notification Hub Tracking ID: {id} | {type} push notification with {success} successes and {failure} failures with a payload of {@payload} and result of {@results}" ,
outcome . TrackingId , type , outcome . Success , outcome . Failure , payload , outcome . Results ) ;
}
2022-08-29 16:06:55 -04:00
}
2021-11-09 11:25:18 -05:00
private string SanitizeTagInput ( string input )
2022-08-29 16:06:55 -04:00
{
2021-11-09 11:25:18 -05:00
// Only allow a-z, A-Z, 0-9, and special characters -_:
return Regex . Replace ( input , "[^a-zA-Z0-9-_:]" , string . Empty ) ;
2017-05-26 22:52:50 -04:00
}
}