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"
}
}
}