2022-06-29 19:46:41 -04:00
using System.Text.Json ;
2021-11-09 11:25:18 -05:00
using System.Text.RegularExpressions ;
2024-12-18 16:31:07 +01:00
using Bit.Core.AdminConsole.Entities ;
2023-04-14 13:25:56 -04:00
using Bit.Core.Auth.Entities ;
2021-02-04 12:54:21 -06:00
using Bit.Core.Context ;
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 ;
2025-01-06 12:10:53 -05:00
using Bit.Core.Platform.Push ;
2019-03-19 00:39:03 -04:00
using Bit.Core.Repositories ;
2023-04-18 14:05:17 +02:00
using Bit.Core.Tools.Entities ;
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 ;
2022-10-19 10:22:40 -04:00
using Microsoft.Extensions.Logging ;
2017-05-26 22:52:50 -04:00
2024-10-22 09:20:57 -07:00
namespace Bit.Core.NotificationHub ;
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 IHttpContextAccessor _httpContextAccessor ;
2024-04-08 15:39:44 -04:00
private readonly bool _enableTracing = false ;
2024-10-22 09:20:57 -07:00
private readonly INotificationHubPool _notificationHubPool ;
2024-04-08 15:39:44 -04:00
private readonly ILogger _logger ;
2022-08-29 16:06:55 -04:00
2017-05-26 22:52:50 -04:00
public NotificationHubPushNotificationService (
IInstallationDeviceRepository installationDeviceRepository ,
2024-10-22 09:20:57 -07:00
INotificationHubPool notificationHubPool ,
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 ;
2017-05-26 22:52:50 -04:00
_httpContextAccessor = httpContextAccessor ;
2024-10-22 09:20:57 -07:00
_notificationHubPool = notificationHubPool ;
2022-10-19 10:22:40 -04:00
_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 ) ;
}
2024-02-14 04:15:07 +10:00
public async Task PushSyncOrganizationsAsync ( Guid userId )
{
await PushUserAsync ( userId , PushType . SyncOrganizations ) ;
}
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
2024-12-18 16:31:07 +01:00
public async Task PushSyncOrganizationStatusAsync ( Organization organization )
{
var message = new OrganizationStatusPushNotification
{
OrganizationId = organization . Id ,
Enabled = organization . Enabled
} ;
await SendPayloadToOrganizationAsync ( organization . Id , PushType . SyncOrganizationStatusChanged , message , false ) ;
}
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
{
2024-10-22 09:20:57 -07:00
var results = await _notificationHubPool . AllClients . SendTemplateNotificationAsync (
new Dictionary < string , string >
{
{ "type" , ( ( byte ) type ) . ToString ( ) } ,
{ "payload" , JsonSerializer . Serialize ( payload ) }
} , tag ) ;
2024-04-08 15:39:44 -04:00
if ( _enableTracing )
{
2024-10-22 09:20:57 -07:00
foreach ( var ( client , outcome ) in results )
2024-04-08 15:39:44 -04:00
{
2024-10-22 09:20:57 -07:00
if ( ! client . EnableTestSend )
2024-04-08 15:39:44 -04:00
{
2024-10-22 09:20:57 -07:00
continue ;
2024-04-08 15:39:44 -04:00
}
2024-10-22 09:20:57 -07:00
_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 ) ;
2024-04-08 15:39:44 -04:00
}
2022-10-19 10:22:40 -04:00
}
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
}
}