diff --git a/src/Api/Controllers/DevicesController.cs b/src/Api/Controllers/DevicesController.cs index 1d64c3e362..4535f9f35d 100644 --- a/src/Api/Controllers/DevicesController.cs +++ b/src/Api/Controllers/DevicesController.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Authorization; using Bit.Core.Models.Api; using Bit.Core.Exceptions; using Bit.Core.Models.Table; -using Microsoft.AspNetCore.Identity; using Bit.Core.Services; namespace Bit.Api.Controllers @@ -109,7 +108,13 @@ namespace Bit.Api.Controllers [HttpPost("identifier/{identifier}/clear-token")] public async Task PutClearToken(string identifier) { - await _deviceRepository.ClearPushTokenByIdentifierAsync(identifier); + var device = await _deviceRepository.GetByIdentifierAsync(identifier, _userService.GetProperUserId(User).Value); + if(device == null) + { + throw new NotFoundException(); + } + + await _deviceService.ClearTokenAsync(device); } [HttpDelete("{id}")] @@ -122,7 +127,7 @@ namespace Bit.Api.Controllers throw new NotFoundException(); } - await _deviceRepository.DeleteAsync(device); + await _deviceService.DeleteAsync(device); } } } diff --git a/src/Api/settings.json b/src/Api/settings.json index f407acb316..642be3b400 100644 --- a/src/Api/settings.json +++ b/src/Api/settings.json @@ -30,6 +30,10 @@ "documentDb": { "uri": "SECRET", "key": "SECRET" + }, + "notificationHub": { + "connectionString": "SECRET", + "hubName": "SECRET" } }, "IpRateLimitOptions": { diff --git a/src/Billing/settings.json b/src/Billing/settings.json index 3414ba4825..a6d8e2e760 100644 --- a/src/Billing/settings.json +++ b/src/Billing/settings.json @@ -30,6 +30,10 @@ "documentDb": { "uri": "SECRET", "key": "SECRET" + }, + "notificationHub": { + "connectionString": "SECRET", + "hubName": "SECRET" } }, "billingSettings": { diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 48d085f0b3..41921a7436 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -20,6 +20,7 @@ + diff --git a/src/Core/Enums/DeviceType.cs b/src/Core/Enums/DeviceType.cs index 8caf8a35fb..4b1c5872df 100644 --- a/src/Core/Enums/DeviceType.cs +++ b/src/Core/Enums/DeviceType.cs @@ -16,6 +16,7 @@ OperaBrowser = 11, EdgeBrowser = 12, IEBrowser = 13, - UnknownBrowser = 14 + UnknownBrowser = 14, + AndroidAmazon = 15 } } diff --git a/src/Core/GlobalSettings.cs b/src/Core/GlobalSettings.cs index 2a072219e6..0c93c9e41e 100644 --- a/src/Core/GlobalSettings.cs +++ b/src/Core/GlobalSettings.cs @@ -13,6 +13,7 @@ public virtual IdentityServerSettings IdentityServer { get; set; } = new IdentityServerSettings(); public virtual DataProtectionSettings DataProtection { get; set; } = new DataProtectionSettings(); public virtual DocumentDbSettings DocumentDb { get; set; } = new DocumentDbSettings(); + public virtual NotificationHubSettings NotificationHub { get; set; } = new NotificationHubSettings(); public class SqlServerSettings { @@ -54,5 +55,11 @@ public string Uri { get; set; } public string Key { get; set; } } + + public class NotificationHubSettings + { + public string ConnectionString { get; set; } + public string HubName { get; set; } + } } } diff --git a/src/Core/Identity/JwtBearerSignInManager.cs b/src/Core/Identity/JwtBearerSignInManager.cs index e61d38bc07..1e400cfb5a 100644 --- a/src/Core/Identity/JwtBearerSignInManager.cs +++ b/src/Core/Identity/JwtBearerSignInManager.cs @@ -10,12 +10,14 @@ using Bit.Core.Models.Table; using Microsoft.AspNetCore.Builder; using Microsoft.IdentityModel.Tokens; using Bit.Core.Repositories; +using Bit.Core.Services; namespace Bit.Core.Identity { public class JwtBearerSignInManager { private readonly IDeviceRepository _deviceRepository; + private readonly IDeviceService _deviceService; public JwtBearerSignInManager( UserManager userManager, @@ -25,7 +27,8 @@ namespace Bit.Core.Identity IOptions jwtIdentityOptionsAccessor, IOptions jwtOptionsAccessor, ILogger logger, - IDeviceRepository deviceRepository) + IDeviceRepository deviceRepository, + IDeviceService deviceService) { UserManager = userManager; Context = contextAccessor.HttpContext; @@ -34,6 +37,7 @@ namespace Bit.Core.Identity JwtIdentityOptions = jwtIdentityOptionsAccessor?.Value ?? new JwtBearerIdentityOptions(); JwtBearerOptions = jwtOptionsAccessor?.Value ?? new JwtBearerOptions(); _deviceRepository = deviceRepository; + _deviceService = deviceService; } internal UserManager UserManager { get; set; } @@ -75,7 +79,7 @@ namespace Bit.Core.Identity if(existingDevice == null) { device.UserId = user.Id; - await _deviceRepository.CreateAsync(device); + await _deviceService.SaveAsync(device); } } @@ -117,7 +121,7 @@ namespace Bit.Core.Identity if(existingDevice == null) { device.UserId = user.Id; - await _deviceRepository.CreateAsync(device); + await _deviceService.SaveAsync(device); } } diff --git a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs index 7e358fb85a..4568a451fc 100644 --- a/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs +++ b/src/Core/IdentityServer/ResourceOwnerPasswordValidator.cs @@ -16,6 +16,7 @@ using System.Collections.Generic; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; +using Bit.Core.Services; namespace Bit.Core.IdentityServer { @@ -26,18 +27,21 @@ namespace Bit.Core.IdentityServer private JwtBearerOptions _jwtBearerOptions; private JwtBearerIdentityOptions _jwtBearerIdentityOptions; private readonly IDeviceRepository _deviceRepository; + private readonly IDeviceService _deviceService; public ResourceOwnerPasswordValidator( UserManager userManager, IOptions identityOptionsAccessor, IOptions jwtIdentityOptionsAccessor, - IDeviceRepository deviceRepository) + IDeviceRepository deviceRepository, + IDeviceService deviceService) { _userManager = userManager; _identityOptions = identityOptionsAccessor?.Value ?? new IdentityOptions(); _jwtBearerIdentityOptions = jwtIdentityOptionsAccessor?.Value; - _jwtBearerOptions = Core.Identity.JwtBearerAppBuilderExtensions.BuildJwtBearerOptions(_jwtBearerIdentityOptions); + _jwtBearerOptions = Identity.JwtBearerAppBuilderExtensions.BuildJwtBearerOptions(_jwtBearerIdentityOptions); _deviceRepository = deviceRepository; + _deviceService = deviceService; } public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) @@ -222,7 +226,7 @@ namespace Bit.Core.IdentityServer if(existingDevice == null) { device.UserId = user.Id; - await _deviceRepository.CreateAsync(device); + await _deviceService.SaveAsync(device); return device; } diff --git a/src/Core/Services/IDeviceService.cs b/src/Core/Services/IDeviceService.cs index 5bfebbc178..ea5a1b7df0 100644 --- a/src/Core/Services/IDeviceService.cs +++ b/src/Core/Services/IDeviceService.cs @@ -6,5 +6,7 @@ namespace Bit.Core.Services public interface IDeviceService { Task SaveAsync(Device device); + Task ClearTokenAsync(Device device); + Task DeleteAsync(Device device); } } diff --git a/src/Core/Services/IPushRegistrationService.cs b/src/Core/Services/IPushRegistrationService.cs new file mode 100644 index 0000000000..c0755fae23 --- /dev/null +++ b/src/Core/Services/IPushRegistrationService.cs @@ -0,0 +1,12 @@ +using System; +using System.Threading.Tasks; +using Bit.Core.Models.Table; + +namespace Bit.Core.Services +{ + public interface IPushRegistrationService + { + Task CreateOrUpdateRegistrationAsync(Device device); + Task DeleteRegistrationAsync(Guid deviceId); + } +} diff --git a/src/Core/Services/Implementations/DeviceService.cs b/src/Core/Services/Implementations/DeviceService.cs index 712c6c5124..ed9d00e11e 100644 --- a/src/Core/Services/Implementations/DeviceService.cs +++ b/src/Core/Services/Implementations/DeviceService.cs @@ -8,11 +8,14 @@ namespace Bit.Core.Services public class DeviceService : IDeviceService { private readonly IDeviceRepository _deviceRepository; + private readonly IPushRegistrationService _pushRegistrationService; public DeviceService( - IDeviceRepository deviceRepository) + IDeviceRepository deviceRepository, + IPushRegistrationService pushRegistrationService) { _deviceRepository = deviceRepository; + _pushRegistrationService = pushRegistrationService; } public async Task SaveAsync(Device device) @@ -26,6 +29,20 @@ namespace Bit.Core.Services device.RevisionDate = DateTime.UtcNow; await _deviceRepository.ReplaceAsync(device); } + + await _pushRegistrationService.CreateOrUpdateRegistrationAsync(device); + } + + public async Task ClearTokenAsync(Device device) + { + await _deviceRepository.ClearPushTokenByIdentifierAsync(device.Identifier); + await _pushRegistrationService.DeleteRegistrationAsync(device.Id); + } + + public async Task DeleteAsync(Device device) + { + await _deviceRepository.DeleteAsync(device); + await _pushRegistrationService.DeleteRegistrationAsync(device.Id); } } } diff --git a/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs b/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs new file mode 100644 index 0000000000..1010636d4f --- /dev/null +++ b/src/Core/Services/Implementations/NotificationHubPushRegistrationService.cs @@ -0,0 +1,66 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.Azure.NotificationHubs; +using Bit.Core.Models.Table; + +namespace Bit.Core.Services +{ + public class NotificationHubPushRegistrationService : IPushRegistrationService + { + private readonly NotificationHubClient _client; + + public NotificationHubPushRegistrationService(GlobalSettings globalSettings) + { + _client = NotificationHubClient.CreateClientFromConnectionString(globalSettings.NotificationHub.ConnectionString, + globalSettings.NotificationHub.HubName); + } + + public async Task CreateOrUpdateRegistrationAsync(Device device) + { + if(string.IsNullOrWhiteSpace(device.PushToken)) + { + return; + } + + var installation = new Installation + { + InstallationId = device.Id.ToString(), + PushChannel = device.PushToken + }; + + installation.Tags = new List + { + "userId:" + device.UserId.ToString() + }; + + if(!string.IsNullOrWhiteSpace(device.Identifier)) + { + installation.Tags.Add("identifier:" + device.Identifier); + } + + switch(device.Type) + { + case Enums.DeviceType.Android: + installation.Platform = NotificationPlatform.Gcm; + break; + case Enums.DeviceType.iOS: + installation.Platform = NotificationPlatform.Apns; + break; + case Enums.DeviceType.AndroidAmazon: + installation.Platform = NotificationPlatform.Adm; + break; + default: + break; + } + + await _client.CreateOrUpdateInstallationAsync(installation); + } + + public async Task DeleteRegistrationAsync(Guid deviceId) + { + await _client.DeleteInstallationAsync(deviceId.ToString()); + } + } +} diff --git a/src/Core/Services/Implementations/NoopBlockIpService.cs b/src/Core/Services/NoopImplementations/NoopBlockIpService.cs similarity index 100% rename from src/Core/Services/Implementations/NoopBlockIpService.cs rename to src/Core/Services/NoopImplementations/NoopBlockIpService.cs diff --git a/src/Core/Services/Implementations/NoopMailService.cs b/src/Core/Services/NoopImplementations/NoopMailService.cs similarity index 100% rename from src/Core/Services/Implementations/NoopMailService.cs rename to src/Core/Services/NoopImplementations/NoopMailService.cs diff --git a/src/Core/Services/NoopImplementations/NoopPushRegistrationService.cs b/src/Core/Services/NoopImplementations/NoopPushRegistrationService.cs new file mode 100644 index 0000000000..c46677137f --- /dev/null +++ b/src/Core/Services/NoopImplementations/NoopPushRegistrationService.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using Bit.Core.Models.Table; + +namespace Bit.Core.Services +{ + public class NoopPushRegistrationService : IPushRegistrationService + { + public Task CreateOrUpdateRegistrationAsync(Device device) + { + return Task.FromResult(0); + } + + public Task DeleteRegistrationAsync(Guid deviceId) + { + return Task.FromResult(0); + } + } +} diff --git a/src/Core/Services/Implementations/NoopPushService.cs b/src/Core/Services/NoopImplementations/NoopPushService.cs similarity index 100% rename from src/Core/Services/Implementations/NoopPushService.cs rename to src/Core/Services/NoopImplementations/NoopPushService.cs diff --git a/src/Core/Utilities/ServiceCollectionExtensions.cs b/src/Core/Utilities/ServiceCollectionExtensions.cs index 729f061904..0c71f1de69 100644 --- a/src/Core/Utilities/ServiceCollectionExtensions.cs +++ b/src/Core/Utilities/ServiceCollectionExtensions.cs @@ -52,6 +52,7 @@ namespace Bit.Core.Utilities services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } public static void AddNoopServices(this IServiceCollection services) @@ -59,6 +60,7 @@ namespace Bit.Core.Utilities services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); } public static IdentityBuilder AddCustomIdentityServices( diff --git a/src/Identity/settings.json b/src/Identity/settings.json index 4470a98cd0..5354976d37 100644 --- a/src/Identity/settings.json +++ b/src/Identity/settings.json @@ -30,6 +30,10 @@ "documentDb": { "uri": "SECRET", "key": "SECRET" + }, + "notificationHub": { + "connectionString": "SECRET", + "hubName": "SECRET" } } }