diff --git a/bitwarden-core.sln b/bitwarden-core.sln
index a405c0063f..6630550bf1 100644
--- a/bitwarden-core.sln
+++ b/bitwarden-core.sln
@@ -1,10 +1,14 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
-VisualStudioVersion = 15.0.26403.7
+VisualStudioVersion = 15.0.26430.4
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}"
EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "util", "util", "{DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{DD5BD056-4AAE-43EF-BBD2-0B569B8DA84F}"
+EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{458155D3-BCBC-481D-B37A-40D2ED10F0A4}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
@@ -24,6 +28,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Billing", "src\Billing\Bill
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Identity", "src\Identity\Identity.csproj", "{04148736-3C0B-445E-8B74-2020E7A53502}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AnhMigrator", "util\AnhMigrator\AnhMigrator.csproj", "{05C45D68-DAB9-40E6-9E2E-F81BBD2E0C42}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -56,6 +62,10 @@ Global
{04148736-3C0B-445E-8B74-2020E7A53502}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04148736-3C0B-445E-8B74-2020E7A53502}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04148736-3C0B-445E-8B74-2020E7A53502}.Release|Any CPU.Build.0 = Release|Any CPU
+ {05C45D68-DAB9-40E6-9E2E-F81BBD2E0C42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {05C45D68-DAB9-40E6-9E2E-F81BBD2E0C42}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {05C45D68-DAB9-40E6-9E2E-F81BBD2E0C42}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {05C45D68-DAB9-40E6-9E2E-F81BBD2E0C42}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -67,5 +77,6 @@ Global
{B78A6C74-1A24-48C6-802A-13BE3E4DAFF1} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
{02BC2982-ED8D-4A6D-A41E-092B3DAEB98A} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
{04148736-3C0B-445E-8B74-2020E7A53502} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84D}
+ {05C45D68-DAB9-40E6-9E2E-F81BBD2E0C42} = {DD5BD056-4AAE-43EF-BBD2-0B569B8DA84E}
EndGlobalSection
EndGlobal
diff --git a/util/AnhMigrator/AnhMigrator.csproj b/util/AnhMigrator/AnhMigrator.csproj
new file mode 100644
index 0000000000..434be5f69a
--- /dev/null
+++ b/util/AnhMigrator/AnhMigrator.csproj
@@ -0,0 +1,18 @@
+
+
+
+ Exe
+ net461
+ Bit.AnhMigrator
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/util/AnhMigrator/Program.cs b/util/AnhMigrator/Program.cs
new file mode 100644
index 0000000000..a854a7dc14
--- /dev/null
+++ b/util/AnhMigrator/Program.cs
@@ -0,0 +1,166 @@
+using Bit.Core.Models.Table;
+using Dapper;
+using Microsoft.Azure.NotificationHubs;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.SqlClient;
+using System.Linq;
+using System.Threading.Tasks;
+using Bit.Core.Enums;
+using Newtonsoft.Json;
+
+namespace Bit.AnhMigrator
+{
+ class Program
+ {
+ private const string SqlConnectionString = "";
+ private const string AnhConnectionString = "";
+ private const string AnhHubName = "";
+
+ private static DateTime _startDate = DateTime.UtcNow.AddYears(-10);
+ private static string _viewId = "";
+
+ private static NotificationHubClient _client;
+
+ static void Main(string[] args)
+ {
+ _client = NotificationHubClient.CreateClientFromConnectionString(
+ AnhConnectionString,
+ AnhHubName);
+
+ //RegisterAsync(args).Wait();
+ //ViewAsync(args).Wait();
+ Console.Read();
+ }
+
+ private static async Task RegisterAsync(string[] args)
+ {
+ IDictionary> orgUsersDict;
+ using(var connection = new SqlConnection(SqlConnectionString))
+ {
+ var results = await connection.QueryAsync(
+ "SELECT * FROM [dbo].[OrganizationUser] WHERE [Status] = 2 AND [UserId] IS NOT NULL",
+ commandType: CommandType.Text);
+
+ orgUsersDict = results
+ .GroupBy(ou => ou.UserId.Value)
+ .ToDictionary(ou => ou.Key, ou => ou.ToList());
+ }
+
+ IEnumerable devices;
+ using(var connection = new SqlConnection(SqlConnectionString))
+ {
+ devices = await connection.QueryAsync(
+ "SELECT * FROM [dbo].[Device] WHERE [PushToken] IS NOT NULL AND [RevisionDate] > @StartDate",
+ new { StartDate = _startDate },
+ commandType: CommandType.Text);
+ }
+
+ var i = 0;
+ foreach(var device in devices)
+ {
+ i++;
+
+ var installation = new Installation
+ {
+ InstallationId = device.Id.ToString(),
+ PushChannel = device.PushToken,
+ Templates = new Dictionary()
+ };
+
+ installation.Tags = new List
+ {
+ $"userId:{device.UserId}"
+ };
+
+ if(!string.IsNullOrWhiteSpace(device.Identifier))
+ {
+ installation.Tags.Add("deviceIdentifier:" + device.Identifier);
+ }
+
+ if(orgUsersDict.ContainsKey(device.UserId))
+ {
+ foreach(var orgUser in orgUsersDict[device.UserId])
+ {
+ installation.Tags.Add("organizationId:" + orgUser.OrganizationId);
+ }
+ }
+
+ string payloadTemplate = null, messageTemplate = null, badgeMessageTemplate = null;
+ switch(device.Type)
+ {
+ case DeviceType.Android:
+ payloadTemplate = "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}";
+ messageTemplate = "{\"data\":{\"type\":\"#(type)\"}," +
+ "\"notification\":{\"title\":\"$(title)\",\"body\":\"$(message)\"}}";
+
+ installation.Platform = NotificationPlatform.Gcm;
+ break;
+ case 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;
+ break;
+ case DeviceType.AndroidAmazon:
+ payloadTemplate = "{\"data\":{\"type\":\"#(type)\",\"payload\":\"$(payload)\"}}";
+ messageTemplate = "{\"data\":{\"type\":\"#(type)\",\"message\":\"$(message)\"}}";
+
+ installation.Platform = NotificationPlatform.Adm;
+ break;
+ default:
+ 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);
+
+ Console.WriteLine("Added install #" + i + " (" + installation.InstallationId + ")");
+ }
+ }
+
+ private static 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
+ {
+ fullTemplateId,
+ $"{fullTemplateId}_userId:{userId}"
+ }
+ };
+
+ if(!string.IsNullOrWhiteSpace(deviceIdentifier))
+ {
+ template.Tags.Add($"{fullTemplateId}_deviceIdentifier:{deviceIdentifier}");
+ }
+
+ installation.Templates.Add(fullTemplateId, template);
+ }
+
+ private static async Task ViewAsync(string[] args)
+ {
+ var install = await _client.GetInstallationAsync(_viewId);
+ var json = JsonConvert.SerializeObject(install, Formatting.Indented);
+ Console.WriteLine(json);
+ }
+ }
+}