mirror of
https://github.com/bitwarden/server.git
synced 2025-01-10 20:07:56 +01:00
notification hub services
This commit is contained in:
parent
887fe4fc05
commit
5af974d541
@ -8,5 +8,7 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
Task CreateOrUpdateRegistrationAsync(Device device);
|
Task CreateOrUpdateRegistrationAsync(Device device);
|
||||||
Task DeleteRegistrationAsync(Guid deviceId);
|
Task DeleteRegistrationAsync(Guid deviceId);
|
||||||
|
Task AddUserRegistrationOrganizationAsync(Guid userId, Guid organizationId);
|
||||||
|
Task DeleteUserRegistrationOrganizationAsync(Guid userId, Guid organizationId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,183 @@
|
|||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Bit.Core.Models.Table;
|
||||||
|
using Microsoft.Azure.NotificationHubs;
|
||||||
|
using Bit.Core.Enums;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
|
namespace Bit.Core.Services
|
||||||
|
{
|
||||||
|
public class NotificationHubPushNotificationService : IPushNotificationService
|
||||||
|
{
|
||||||
|
private readonly NotificationHubClient _client;
|
||||||
|
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||||
|
|
||||||
|
public NotificationHubPushNotificationService(
|
||||||
|
GlobalSettings globalSettings,
|
||||||
|
IHttpContextAccessor httpContextAccessor)
|
||||||
|
{
|
||||||
|
_client = NotificationHubClient.CreateClientFromConnectionString(globalSettings.NotificationHub.ConnectionString,
|
||||||
|
globalSettings.NotificationHub.HubName);
|
||||||
|
|
||||||
|
_httpContextAccessor = httpContextAccessor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PushSyncCipherCreateAsync(Cipher cipher)
|
||||||
|
{
|
||||||
|
await PushCipherAsync(cipher, PushType.SyncCipherCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PushSyncCipherUpdateAsync(Cipher cipher)
|
||||||
|
{
|
||||||
|
await PushCipherAsync(cipher, PushType.SyncCipherUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PushSyncCipherDeleteAsync(Cipher cipher)
|
||||||
|
{
|
||||||
|
await PushCipherAsync(cipher, PushType.SyncLoginDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PushCipherAsync(Cipher cipher, PushType type)
|
||||||
|
{
|
||||||
|
if(cipher.OrganizationId.HasValue)
|
||||||
|
{
|
||||||
|
// 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.
|
||||||
|
|
||||||
|
// await SendPayloadToOrganizationAsync(cipher.OrganizationId.Value, type, message, true);
|
||||||
|
}
|
||||||
|
else if(cipher.UserId.HasValue)
|
||||||
|
{
|
||||||
|
var message = new SyncCipherPushNotification
|
||||||
|
{
|
||||||
|
Id = cipher.Id,
|
||||||
|
UserId = cipher.UserId,
|
||||||
|
OrganizationId = cipher.OrganizationId,
|
||||||
|
RevisionDate = cipher.RevisionDate,
|
||||||
|
};
|
||||||
|
|
||||||
|
await SendPayloadToUserAsync(cipher.UserId.Value, type, message, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PushSyncFolderCreateAsync(Folder folder)
|
||||||
|
{
|
||||||
|
await PushFolderAsync(folder, PushType.SyncFolderCreate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PushSyncFolderUpdateAsync(Folder folder)
|
||||||
|
{
|
||||||
|
await PushFolderAsync(folder, PushType.SyncFolderUpdate);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PushSyncFolderDeleteAsync(Folder folder)
|
||||||
|
{
|
||||||
|
await PushFolderAsync(folder, PushType.SyncFolderDelete);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PushFolderAsync(Folder folder, PushType type)
|
||||||
|
{
|
||||||
|
var message = new SyncFolderPushNotification
|
||||||
|
{
|
||||||
|
Id = folder.Id,
|
||||||
|
UserId = folder.UserId,
|
||||||
|
RevisionDate = folder.RevisionDate
|
||||||
|
};
|
||||||
|
|
||||||
|
await SendPayloadToUserAsync(folder.UserId, type, message, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PushSyncCiphersAsync(Guid userId)
|
||||||
|
{
|
||||||
|
await PushSyncUserAsync(userId, PushType.SyncCiphers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PushSyncVaultAsync(Guid userId)
|
||||||
|
{
|
||||||
|
await PushSyncUserAsync(userId, PushType.SyncVault);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PushSyncOrgKeysAsync(Guid userId)
|
||||||
|
{
|
||||||
|
await PushSyncUserAsync(userId, PushType.SyncOrgKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task PushSyncSettingsAsync(Guid userId)
|
||||||
|
{
|
||||||
|
await PushSyncUserAsync(userId, PushType.SyncSettings);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PushSyncUserAsync(Guid userId, PushType type)
|
||||||
|
{
|
||||||
|
var message = new SyncUserPushNotification
|
||||||
|
{
|
||||||
|
UserId = userId,
|
||||||
|
Date = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
|
||||||
|
await SendPayloadToUserAsync(userId, type, message, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendPayloadToUserAsync(Guid userId, PushType type, object payload, bool excludeCurrentContext)
|
||||||
|
{
|
||||||
|
var tag = BuildTag($"template:payload_userId:{userId}", excludeCurrentContext);
|
||||||
|
await SendPayloadAsync(tag, type, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendPayloadToOrganizationAsync(Guid orgId, PushType type, object payload, bool excludeCurrentContext)
|
||||||
|
{
|
||||||
|
var tag = BuildTag($"template:payload && organizationId:{orgId}", excludeCurrentContext);
|
||||||
|
await SendPayloadAsync(tag, type, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string BuildTag(string tag, bool excludeCurrentContext)
|
||||||
|
{
|
||||||
|
if(excludeCurrentContext)
|
||||||
|
{
|
||||||
|
var currentContext = _httpContextAccessor?.HttpContext?.
|
||||||
|
RequestServices.GetService(typeof(CurrentContext)) as CurrentContext;
|
||||||
|
if(!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier))
|
||||||
|
{
|
||||||
|
tag += $" && !deviceIdentifier:{currentContext.DeviceIdentifier}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"({tag})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task SendPayloadAsync(string tag, PushType type, object payload)
|
||||||
|
{
|
||||||
|
await _client.SendTemplateNotificationAsync(
|
||||||
|
new Dictionary<string, string>
|
||||||
|
{
|
||||||
|
{ "type", ((byte)type).ToString() },
|
||||||
|
{ "payload", JsonConvert.SerializeObject(payload) }
|
||||||
|
}, tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyncCipherPushNotification
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid? UserId { get; set; }
|
||||||
|
public Guid? OrganizationId { get; set; }
|
||||||
|
public DateTime RevisionDate { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyncFolderPushNotification
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public DateTime RevisionDate { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SyncUserPushNotification
|
||||||
|
{
|
||||||
|
public Guid UserId { get; set; }
|
||||||
|
public DateTime Date { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,20 +1,25 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Microsoft.Azure.NotificationHubs;
|
using Microsoft.Azure.NotificationHubs;
|
||||||
using Bit.Core.Models.Table;
|
using Bit.Core.Models.Table;
|
||||||
|
using Bit.Core.Repositories;
|
||||||
|
|
||||||
namespace Bit.Core.Services
|
namespace Bit.Core.Services
|
||||||
{
|
{
|
||||||
public class NotificationHubPushRegistrationService : IPushRegistrationService
|
public class NotificationHubPushRegistrationService : IPushRegistrationService
|
||||||
{
|
{
|
||||||
private readonly NotificationHubClient _client;
|
private readonly NotificationHubClient _client;
|
||||||
|
private readonly IDeviceRepository _deviceRepository;
|
||||||
|
|
||||||
public NotificationHubPushRegistrationService(GlobalSettings globalSettings)
|
public NotificationHubPushRegistrationService(
|
||||||
|
GlobalSettings globalSettings,
|
||||||
|
IDeviceRepository deviceRepository)
|
||||||
{
|
{
|
||||||
_client = NotificationHubClient.CreateClientFromConnectionString(globalSettings.NotificationHub.ConnectionString,
|
_client = NotificationHubClient.CreateClientFromConnectionString(globalSettings.NotificationHub.ConnectionString,
|
||||||
globalSettings.NotificationHub.HubName);
|
globalSettings.NotificationHub.HubName);
|
||||||
|
|
||||||
|
_deviceRepository = deviceRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task CreateOrUpdateRegistrationAsync(Device device)
|
public async Task CreateOrUpdateRegistrationAsync(Device device)
|
||||||
@ -27,40 +32,115 @@ namespace Bit.Core.Services
|
|||||||
var installation = new Installation
|
var installation = new Installation
|
||||||
{
|
{
|
||||||
InstallationId = device.Id.ToString(),
|
InstallationId = device.Id.ToString(),
|
||||||
PushChannel = device.PushToken
|
PushChannel = device.PushToken,
|
||||||
|
Templates = new Dictionary<string, InstallationTemplate>()
|
||||||
};
|
};
|
||||||
|
|
||||||
installation.Tags = new List<string>
|
installation.Tags = new List<string>
|
||||||
{
|
{
|
||||||
"userId:" + device.UserId.ToString()
|
$"userId:{device.UserId}"
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!string.IsNullOrWhiteSpace(device.Identifier))
|
if(!string.IsNullOrWhiteSpace(device.Identifier))
|
||||||
{
|
{
|
||||||
installation.Tags.Add("identifier:" + device.Identifier);
|
installation.Tags.Add("deviceIdentifier:" + device.Identifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string payloadTemplate = null, messageTemplate = null, badgeMessageTemplate = null;
|
||||||
switch(device.Type)
|
switch(device.Type)
|
||||||
{
|
{
|
||||||
case Enums.DeviceType.Android:
|
case Enums.DeviceType.Android:
|
||||||
|
payloadTemplate = "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}";
|
||||||
|
messageTemplate = "{\"data\":{\"type\":\"#(type)\"}," +
|
||||||
|
"\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}";
|
||||||
|
|
||||||
installation.Platform = NotificationPlatform.Gcm;
|
installation.Platform = NotificationPlatform.Gcm;
|
||||||
break;
|
break;
|
||||||
case Enums.DeviceType.iOS:
|
case Enums.DeviceType.iOS:
|
||||||
|
payloadTemplate = "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}," +
|
||||||
|
"\"aps\":{\"alert\":null,\"badge\":null,\"content-available\":1}}";
|
||||||
|
messageTemplate = "{\"data\":{\"type\":\"#(type)\"}," +
|
||||||
|
"\"aps\":{\"alert\":\"$(message)\",\"badge\":null,\"content-available\":1}}";
|
||||||
|
badgeMessageTemplate = "{\"data\":{\"type\":\"#(type)\"}," +
|
||||||
|
"\"aps\":{\"alert\":\"$(message)\",\"badge\":\"#(badge)\",\"content-available\":1}}";
|
||||||
|
|
||||||
installation.Platform = NotificationPlatform.Apns;
|
installation.Platform = NotificationPlatform.Apns;
|
||||||
break;
|
break;
|
||||||
case Enums.DeviceType.AndroidAmazon:
|
case Enums.DeviceType.AndroidAmazon:
|
||||||
|
payloadTemplate = "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}";
|
||||||
|
messageTemplate = "{\"data\":{\"type\":\"#(type)\",\"message\":\"$(message)\"}}";
|
||||||
|
|
||||||
installation.Platform = NotificationPlatform.Adm;
|
installation.Platform = NotificationPlatform.Adm;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BuildInstallationTemplate(installation, "payload", payloadTemplate, device.UserId, device.Identifier);
|
||||||
|
BuildInstallationTemplate(installation, "message", messageTemplate, device.UserId, device.Identifier);
|
||||||
|
BuildInstallationTemplate(installation, "badgeMessage", badgeMessageTemplate ?? messageTemplate, device.UserId,
|
||||||
|
device.Identifier);
|
||||||
|
|
||||||
await _client.CreateOrUpdateInstallationAsync(installation);
|
await _client.CreateOrUpdateInstallationAsync(installation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BuildInstallationTemplate(Installation installation, string templateId, string templateBody,
|
||||||
|
Guid userId, string deviceIdentifier)
|
||||||
|
{
|
||||||
|
if(templateBody == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullTemplateId = $"template:{templateId}";
|
||||||
|
|
||||||
|
var template = new InstallationTemplate
|
||||||
|
{
|
||||||
|
Body = templateBody,
|
||||||
|
Tags = new List<string>
|
||||||
|
{
|
||||||
|
fullTemplateId,
|
||||||
|
$"{fullTemplateId}_userId:{userId}"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if(!string.IsNullOrWhiteSpace(deviceIdentifier))
|
||||||
|
{
|
||||||
|
template.Tags.Add($"{fullTemplateId}_deviceIdentifier:{deviceIdentifier}");
|
||||||
|
}
|
||||||
|
|
||||||
|
installation.Templates.Add(fullTemplateId, template);
|
||||||
|
}
|
||||||
|
|
||||||
public async Task DeleteRegistrationAsync(Guid deviceId)
|
public async Task DeleteRegistrationAsync(Guid deviceId)
|
||||||
{
|
{
|
||||||
await _client.DeleteInstallationAsync(deviceId.ToString());
|
await _client.DeleteInstallationAsync(deviceId.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task AddUserRegistrationOrganizationAsync(Guid userId, Guid organizationId)
|
||||||
|
{
|
||||||
|
await PatchTagsForUserDevicesAsync(userId, UpdateOperationType.Add, $"organizationId:{organizationId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task DeleteUserRegistrationOrganizationAsync(Guid userId, Guid organizationId)
|
||||||
|
{
|
||||||
|
await PatchTagsForUserDevicesAsync(userId, UpdateOperationType.Remove, $"organizationId:{organizationId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task PatchTagsForUserDevicesAsync(Guid userId, UpdateOperationType op, string tag)
|
||||||
|
{
|
||||||
|
var devices = await _deviceRepository.GetManyByUserIdAsync(userId);
|
||||||
|
var operation = new PartialUpdateOperation
|
||||||
|
{
|
||||||
|
Operation = op,
|
||||||
|
Path = "/tags",
|
||||||
|
Value = tag
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach(var device in devices)
|
||||||
|
{
|
||||||
|
await _client.PatchInstallationAsync(device.Id.ToString(), new List<PartialUpdateOperation> { operation });
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,8 @@ namespace Bit.Core.Services
|
|||||||
private readonly IGroupRepository _groupRepository;
|
private readonly IGroupRepository _groupRepository;
|
||||||
private readonly IDataProtector _dataProtector;
|
private readonly IDataProtector _dataProtector;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
private readonly IPushNotificationService _pushService;
|
private readonly IPushNotificationService _pushNotificationService;
|
||||||
|
private readonly IPushRegistrationService _pushRegistrationService;
|
||||||
|
|
||||||
public OrganizationService(
|
public OrganizationService(
|
||||||
IOrganizationRepository organizationRepository,
|
IOrganizationRepository organizationRepository,
|
||||||
@ -34,7 +35,8 @@ namespace Bit.Core.Services
|
|||||||
IGroupRepository groupRepository,
|
IGroupRepository groupRepository,
|
||||||
IDataProtectionProvider dataProtectionProvider,
|
IDataProtectionProvider dataProtectionProvider,
|
||||||
IMailService mailService,
|
IMailService mailService,
|
||||||
IPushNotificationService pushService)
|
IPushNotificationService pushNotificationService,
|
||||||
|
IPushRegistrationService pushRegistrationService)
|
||||||
{
|
{
|
||||||
_organizationRepository = organizationRepository;
|
_organizationRepository = organizationRepository;
|
||||||
_organizationUserRepository = organizationUserRepository;
|
_organizationUserRepository = organizationUserRepository;
|
||||||
@ -43,7 +45,8 @@ namespace Bit.Core.Services
|
|||||||
_groupRepository = groupRepository;
|
_groupRepository = groupRepository;
|
||||||
_dataProtector = dataProtectionProvider.CreateProtector("OrganizationServiceDataProtector");
|
_dataProtector = dataProtectionProvider.CreateProtector("OrganizationServiceDataProtector");
|
||||||
_mailService = mailService;
|
_mailService = mailService;
|
||||||
_pushService = pushService;
|
_pushNotificationService = pushNotificationService;
|
||||||
|
_pushRegistrationService = pushRegistrationService;
|
||||||
}
|
}
|
||||||
public async Task<OrganizationBilling> GetBillingAsync(Organization organization)
|
public async Task<OrganizationBilling> GetBillingAsync(Organization organization)
|
||||||
{
|
{
|
||||||
@ -609,7 +612,9 @@ namespace Bit.Core.Services
|
|||||||
await _organizationUserRepository.CreateAsync(orgUser);
|
await _organizationUserRepository.CreateAsync(orgUser);
|
||||||
|
|
||||||
// push
|
// push
|
||||||
await _pushService.PushSyncOrgKeysAsync(signup.Owner.Id);
|
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(orgUser.UserId.Value,
|
||||||
|
organization.Id);
|
||||||
|
await _pushNotificationService.PushSyncOrgKeysAsync(signup.Owner.Id);
|
||||||
|
|
||||||
return new Tuple<Organization, OrganizationUser>(organization, orgUser);
|
return new Tuple<Organization, OrganizationUser>(organization, orgUser);
|
||||||
}
|
}
|
||||||
@ -868,7 +873,8 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
// push
|
// push
|
||||||
await _pushService.PushSyncOrgKeysAsync(orgUser.UserId.Value);
|
await _pushRegistrationService.AddUserRegistrationOrganizationAsync(orgUser.UserId.Value, organizationId);
|
||||||
|
await _pushNotificationService.PushSyncOrgKeysAsync(orgUser.UserId.Value);
|
||||||
|
|
||||||
return orgUser;
|
return orgUser;
|
||||||
}
|
}
|
||||||
@ -915,6 +921,9 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _organizationUserRepository.DeleteAsync(orgUser);
|
await _organizationUserRepository.DeleteAsync(orgUser);
|
||||||
|
|
||||||
|
// push
|
||||||
|
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(orgUser.UserId.Value, organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task DeleteUserAsync(Guid organizationId, Guid userId)
|
public async Task DeleteUserAsync(Guid organizationId, Guid userId)
|
||||||
@ -932,6 +941,9 @@ namespace Bit.Core.Services
|
|||||||
}
|
}
|
||||||
|
|
||||||
await _organizationUserRepository.DeleteAsync(orgUser);
|
await _organizationUserRepository.DeleteAsync(orgUser);
|
||||||
|
|
||||||
|
// push
|
||||||
|
await _pushRegistrationService.DeleteUserRegistrationOrganizationAsync(orgUser.UserId.Value, organizationId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ImportAsync(Guid organizationId,
|
public async Task ImportAsync(Guid organizationId,
|
||||||
|
@ -6,6 +6,11 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
public class NoopPushRegistrationService : IPushRegistrationService
|
public class NoopPushRegistrationService : IPushRegistrationService
|
||||||
{
|
{
|
||||||
|
public Task AddUserRegistrationOrganizationAsync(Guid userId, Guid organizationId)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
|
|
||||||
public Task CreateOrUpdateRegistrationAsync(Device device)
|
public Task CreateOrUpdateRegistrationAsync(Device device)
|
||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
@ -15,5 +20,10 @@ namespace Bit.Core.Services
|
|||||||
{
|
{
|
||||||
return Task.FromResult(0);
|
return Task.FromResult(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Task DeleteUserRegistrationOrganizationAsync(Guid userId, Guid organizationId)
|
||||||
|
{
|
||||||
|
return Task.FromResult(0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,7 +50,7 @@ namespace Bit.Core.Utilities
|
|||||||
public static void AddDefaultServices(this IServiceCollection services)
|
public static void AddDefaultServices(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddSingleton<IMailService, SendGridMailService>();
|
services.AddSingleton<IMailService, SendGridMailService>();
|
||||||
services.AddSingleton<IPushNotificationService, PushSharpPushNotificationService>();
|
services.AddSingleton<IPushNotificationService, NotificationHubPushNotificationService>();
|
||||||
services.AddSingleton<IBlockIpService, AzureQueueBlockIpService>();
|
services.AddSingleton<IBlockIpService, AzureQueueBlockIpService>();
|
||||||
services.AddSingleton<IPushRegistrationService, NotificationHubPushRegistrationService>();
|
services.AddSingleton<IPushRegistrationService, NotificationHubPushRegistrationService>();
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user