diff --git a/src/Api/Api.csproj b/src/Api/Api.csproj index ab58a12375..d19830b90d 100644 --- a/src/Api/Api.csproj +++ b/src/Api/Api.csproj @@ -28,7 +28,7 @@ - + diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index e54be78ab4..28d5636987 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -278,7 +278,7 @@ namespace Bit.Api.Controllers if(userId.HasValue) { var date = await _userService.GetAccountRevisionDateByIdAsync(userId.Value); - revisionDate = Core.Utilities.CoreHelpers.ToEpocMilliseconds(date); + revisionDate = CoreHelpers.ToEpocMilliseconds(date); } return revisionDate; diff --git a/src/Api/settings.json b/src/Api/settings.json index 15004a2ac1..dfa53f045b 100644 --- a/src/Api/settings.json +++ b/src/Api/settings.json @@ -2,7 +2,6 @@ "globalSettings": { "siteName": "bitwarden", "baseVaultUri": "http://localhost:4001/#", - "jwtSigningKey": "THIS IS A SECRET. IT KEEPS YOUR TOKEN SAFE. :)", "stripeApiKey": "SECRET", "sqlServer": { "connectionString": "SECRET" @@ -11,13 +10,6 @@ "apiKey": "SECRET", "replyToEmail": "hello@bitwarden.com" }, - "push": { - "apnsCertificateThumbprint": "SECRET", - "apnsCertificatePassword": "SECRET", - "gcmSenderId": "SECRET", - "gcmApiKey": "SECRET", - "gcmAppPackageName": "com.x8bit.bitwarden" - }, "identityServer": { "certificateThumbprint": "SECRET" }, diff --git a/src/Billing/settings.json b/src/Billing/settings.json index a6d8e2e760..06954fb44c 100644 --- a/src/Billing/settings.json +++ b/src/Billing/settings.json @@ -2,7 +2,6 @@ "globalSettings": { "siteName": "bitwarden", "baseVaultUri": "http://localhost:4001/#", - "jwtSigningKey": "THIS IS A SECRET. IT KEEPS YOUR TOKEN SAFE. :)", "stripeApiKey": "SECRET", "sqlServer": { "connectionString": "SECRET" @@ -11,13 +10,6 @@ "apiKey": "SECRET", "replyToEmail": "hello@bitwarden.com" }, - "push": { - "apnsCertificateThumbprint": "SECRET", - "apnsCertificatePassword": "SECRET", - "gcmSenderId": "SECRET", - "gcmApiKey": "SECRET", - "gcmAppPackageName": "com.x8bit.bitwarden" - }, "identityServer": { "certificateThumbprint": "SECRET" }, diff --git a/src/Core/Core.csproj b/src/Core/Core.csproj index 9214db84c8..fbd297beda 100644 --- a/src/Core/Core.csproj +++ b/src/Core/Core.csproj @@ -52,16 +52,15 @@ - + - - + - + diff --git a/src/Core/GlobalSettings.cs b/src/Core/GlobalSettings.cs index aa22660be9..0a11540be7 100644 --- a/src/Core/GlobalSettings.cs +++ b/src/Core/GlobalSettings.cs @@ -4,11 +4,9 @@ { public virtual string SiteName { get; set; } public virtual string BaseVaultUri { get; set; } - public virtual string JwtSigningKey { get; set; } public virtual string StripeApiKey { get; set; } public virtual SqlServerSettings SqlServer { get; set; } = new SqlServerSettings(); public virtual MailSettings Mail { get; set; } = new MailSettings(); - public virtual PushSettings Push { get; set; } = new PushSettings(); public virtual StorageSettings Storage { get; set; } = new StorageSettings(); public virtual AttachmentSettings Attachment { get; set; } = new AttachmentSettings(); public virtual IdentityServerSettings IdentityServer { get; set; } = new IdentityServerSettings(); @@ -51,15 +49,6 @@ } } - public class PushSettings - { - public string ApnsCertificateThumbprint { get; set; } - public string ApnsCertificatePassword { get; set; } - public string GcmSenderId { get; set; } - public string GcmApiKey { get; set; } - public string GcmAppPackageName { get; set; } - } - public class IdentityServerSettings { public string CertificateThumbprint { get; set; } diff --git a/src/Core/IDataObject.cs b/src/Core/IDataObject.cs deleted file mode 100644 index 4796c75771..0000000000 --- a/src/Core/IDataObject.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Bit.Core -{ - public interface IDataObject where T : IEquatable - { - T Id { get; set; } - void SetNewId(); - } -} diff --git a/src/Core/Identity/DuoTokenProvider.cs b/src/Core/Identity/DuoTokenProvider.cs deleted file mode 100644 index d6b03c97ba..0000000000 --- a/src/Core/Identity/DuoTokenProvider.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Threading.Tasks; -using Microsoft.AspNetCore.Identity; -using Bit.Core.Models.Table; -using Bit.Core.Enums; -using Bit.Core.Utilities.Duo; -using System.Collections.Generic; -using System.Net.Http; - -namespace Bit.Core.Identity -{ - public class DuoTokenProvider : IUserTwoFactorTokenProvider - { - public Task CanGenerateTwoFactorTokenAsync(UserManager manager, User user) - { - if(!user.Premium) - { - return Task.FromResult(false); - } - - var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); - var canGenerate = user.TwoFactorProviderIsEnabled(TwoFactorProviderType.Duo) - && !string.IsNullOrWhiteSpace((string)provider?.MetaData["UserId"]); - - return Task.FromResult(canGenerate); - } - - /// Ex: "auto", "push", "passcode:123456", "sms", "phone" - public async Task GenerateAsync(string purpose, UserManager manager, User user) - { - if(!user.Premium) - { - return null; - } - - var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); - var duoClient = new DuoApi((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"], - (string)provider.MetaData["Host"]); - var parts = purpose.Split(':'); - - var parameters = new Dictionary - { - ["async"] = "1", - ["user_id"] = (string)provider.MetaData["UserId"], - ["factor"] = parts[0] - }; - - if(parameters["factor"] == "passcode" && parts.Length > 1) - { - parameters["passcode"] = parts[1]; - } - else - { - parameters["device"] = "auto"; - } - - try - { - var response = await duoClient.JSONApiCallAsync>(HttpMethod.Post, - "/auth/v2/auth", parameters); - - if(response.ContainsKey("txid")) - { - var txId = response["txid"] as string; - return txId; - } - } - catch(DuoException) { } - - return null; - } - - public async Task ValidateAsync(string purpose, string token, UserManager manager, User user) - { - if(!user.Premium) - { - return false; - } - - var provider = user.GetTwoFactorProvider(TwoFactorProviderType.Duo); - var duoClient = new DuoApi((string)provider.MetaData["IKey"], (string)provider.MetaData["SKey"], - (string)provider.MetaData["Host"]); - - var parameters = new Dictionary - { - ["txid"] = token - }; - - try - { - var response = await duoClient.JSONApiCallAsync>(HttpMethod.Get, - "/auth/v2/auth_status", parameters); - - var result = response["result"] as string; - return string.Equals(result, "allow"); - } - catch(DuoException) - { - // TODO: We might want to return true in some cases? What if Duo is down? - } - - return false; - } - } -} diff --git a/src/Core/Models/Table/Cipher.cs b/src/Core/Models/Table/Cipher.cs index ecbb7af3fa..07fe909876 100644 --- a/src/Core/Models/Table/Cipher.cs +++ b/src/Core/Models/Table/Cipher.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; namespace Bit.Core.Models.Table { - public class Cipher : IDataObject + public class Cipher : ITableObject { private Dictionary _attachmentData; diff --git a/src/Core/Models/Table/Collection.cs b/src/Core/Models/Table/Collection.cs index 50aeaf2b28..d6a0bdecf6 100644 --- a/src/Core/Models/Table/Collection.cs +++ b/src/Core/Models/Table/Collection.cs @@ -3,7 +3,7 @@ using Bit.Core.Utilities; namespace Bit.Core.Models.Table { - public class Collection : IDataObject + public class Collection : ITableObject { public Guid Id { get; set; } public Guid OrganizationId { get; set; } diff --git a/src/Core/Models/Table/Device.cs b/src/Core/Models/Table/Device.cs index 21152a4904..4fb20652df 100644 --- a/src/Core/Models/Table/Device.cs +++ b/src/Core/Models/Table/Device.cs @@ -3,7 +3,7 @@ using Bit.Core.Utilities; namespace Bit.Core.Models.Table { - public class Device : IDataObject + public class Device : ITableObject { public Guid Id { get; set; } public Guid UserId { get; set; } diff --git a/src/Core/Models/Table/Folder.cs b/src/Core/Models/Table/Folder.cs index cdfedff4b2..ec20cc3ec0 100644 --- a/src/Core/Models/Table/Folder.cs +++ b/src/Core/Models/Table/Folder.cs @@ -3,7 +3,7 @@ using Bit.Core.Utilities; namespace Bit.Core.Models.Table { - public class Folder : IDataObject + public class Folder : ITableObject { public Guid Id { get; set; } public Guid UserId { get; set; } diff --git a/src/Core/Models/Table/Group.cs b/src/Core/Models/Table/Group.cs index e914fcd59a..502e2faf60 100644 --- a/src/Core/Models/Table/Group.cs +++ b/src/Core/Models/Table/Group.cs @@ -3,7 +3,7 @@ using Bit.Core.Utilities; namespace Bit.Core.Models.Table { - public class Group : IDataObject + public class Group : ITableObject { public Guid Id { get; set; } public Guid OrganizationId { get; set; } diff --git a/src/Core/Models/Table/ITableObject.cs b/src/Core/Models/Table/ITableObject.cs new file mode 100644 index 0000000000..8fb6a41946 --- /dev/null +++ b/src/Core/Models/Table/ITableObject.cs @@ -0,0 +1,10 @@ +using System; + +namespace Bit.Core.Models.Table +{ + public interface ITableObject where T : IEquatable + { + T Id { get; set; } + void SetNewId(); + } +} diff --git a/src/Core/Models/Table/Organization.cs b/src/Core/Models/Table/Organization.cs index d25ddb51ca..36288cb53d 100644 --- a/src/Core/Models/Table/Organization.cs +++ b/src/Core/Models/Table/Organization.cs @@ -4,7 +4,7 @@ using Bit.Core.Enums; namespace Bit.Core.Models.Table { - public class Organization : IDataObject, ISubscriber, IStorable, IStorableSubscriber, IRevisable + public class Organization : ITableObject, ISubscriber, IStorable, IStorableSubscriber, IRevisable { public Guid Id { get; set; } public string Name { get; set; } diff --git a/src/Core/Models/Table/OrganizationUser.cs b/src/Core/Models/Table/OrganizationUser.cs index 34f3ca0710..7b2b5d55d7 100644 --- a/src/Core/Models/Table/OrganizationUser.cs +++ b/src/Core/Models/Table/OrganizationUser.cs @@ -4,7 +4,7 @@ using Bit.Core.Enums; namespace Bit.Core.Models.Table { - public class OrganizationUser : IDataObject + public class OrganizationUser : ITableObject { public Guid Id { get; set; } public Guid OrganizationId { get; set; } diff --git a/src/Core/Models/Table/U2f.cs b/src/Core/Models/Table/U2f.cs index a376e5186c..e8a97c7fe5 100644 --- a/src/Core/Models/Table/U2f.cs +++ b/src/Core/Models/Table/U2f.cs @@ -2,7 +2,7 @@ namespace Bit.Core.Models.Table { - public class U2f : IDataObject + public class U2f : ITableObject { public int Id { get; set; } public Guid UserId { get; set; } diff --git a/src/Core/Models/Table/User.cs b/src/Core/Models/Table/User.cs index 4820f95192..0f2e7bd814 100644 --- a/src/Core/Models/Table/User.cs +++ b/src/Core/Models/Table/User.cs @@ -7,7 +7,7 @@ using System.Linq; namespace Bit.Core.Models.Table { - public class User : IDataObject, ISubscriber, IStorable, IStorableSubscriber, IRevisable + public class User : ITableObject, ISubscriber, IStorable, IStorableSubscriber, IRevisable { private Dictionary _twoFactorProviders; diff --git a/src/Core/Repositories/IRepository.cs b/src/Core/Repositories/IRepository.cs index d577f35c31..bbb4e81af1 100644 --- a/src/Core/Repositories/IRepository.cs +++ b/src/Core/Repositories/IRepository.cs @@ -1,9 +1,10 @@ -using System; +using Bit.Core.Models.Table; +using System; using System.Threading.Tasks; namespace Bit.Core.Repositories { - public interface IRepository where TId : IEquatable where T : class, IDataObject + public interface IRepository where TId : IEquatable where T : class, ITableObject { Task GetByIdAsync(TId id); Task CreateAsync(T obj); diff --git a/src/Core/Repositories/SqlServer/Repository.cs b/src/Core/Repositories/SqlServer/Repository.cs index 6352e4fad6..f8d4d7f919 100644 --- a/src/Core/Repositories/SqlServer/Repository.cs +++ b/src/Core/Repositories/SqlServer/Repository.cs @@ -4,10 +4,13 @@ using System.Data.SqlClient; using System.Linq; using System.Threading.Tasks; using Dapper; +using Bit.Core.Models.Table; namespace Bit.Core.Repositories.SqlServer { - public abstract class Repository : BaseRepository, IRepository where TId : IEquatable where T : class, IDataObject + public abstract class Repository : BaseRepository, IRepository + where TId : IEquatable + where T : class, ITableObject { public Repository(string connectionString, string schema = null, string table = null) : base(connectionString) diff --git a/src/Core/Services/Implementations/PushSharpPushNotificationService.cs b/src/Core/Services/Implementations/PushSharpPushNotificationService.cs deleted file mode 100644 index 1357725238..0000000000 --- a/src/Core/Services/Implementations/PushSharpPushNotificationService.cs +++ /dev/null @@ -1,352 +0,0 @@ -using System; -using System.Linq; -using System.Collections.Generic; -using System.Threading.Tasks; -using Bit.Core.Repositories; -using Newtonsoft.Json.Linq; -using PushSharp.Google; -using PushSharp.Apple; -using Microsoft.AspNetCore.Hosting; -using PushSharp.Core; -using Bit.Core.Models.Table; -using Bit.Core.Enums; -using Newtonsoft.Json; -using Microsoft.Extensions.Logging; -using System.Diagnostics; -using Bit.Core.Utilities; -using Microsoft.AspNetCore.Http; -using Bit.Core.Models; - -namespace Bit.Core.Services -{ - [Obsolete] - public class PushSharpPushNotificationService : IPushNotificationService - { - private readonly IDeviceRepository _deviceRepository; - private readonly ILogger _logger; - private readonly IHttpContextAccessor _httpContextAccessor; - private GcmServiceBroker _gcmBroker; - private ApnsServiceBroker _apnsBroker; - - public PushSharpPushNotificationService( - IDeviceRepository deviceRepository, - IHttpContextAccessor httpContextAccessor, - ILogger logger, - IHostingEnvironment hostingEnvironment, - GlobalSettings globalSettings) - { - _deviceRepository = deviceRepository; - _httpContextAccessor = httpContextAccessor; - _logger = logger; - - InitGcmBroker(globalSettings); - InitApnsBroker(globalSettings, hostingEnvironment); - } - - 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) - { - switch(cipher.Type) - { - case CipherType.Login: - await PushCipherAsync(cipher, PushType.SyncLoginDelete); - break; - default: - break; - } - } - - 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 PushCipherAsync(Cipher cipher, PushType type) - { - if(!cipher.UserId.HasValue) - { - // No push for org ciphers at the moment. - return; - } - - var message = new SyncCipherPushNotification - { - Id = cipher.Id, - UserId = cipher.UserId, - OrganizationId = cipher.OrganizationId, - RevisionDate = cipher.RevisionDate - }; - - var excludedTokens = new List(); - var currentContext = _httpContextAccessor?.HttpContext?. - RequestServices.GetService(typeof(CurrentContext)) as CurrentContext; - if(!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier)) - { - excludedTokens.Add(currentContext.DeviceIdentifier); - } - - await PushToAllUserDevicesAsync(cipher.UserId.Value, type, message, excludedTokens); - } - - private async Task PushFolderAsync(Folder folder, PushType type) - { - var message = new SyncFolderPushNotification - { - Id = folder.Id, - UserId = folder.UserId, - RevisionDate = folder.RevisionDate - }; - - var excludedTokens = new List(); - var currentContext = _httpContextAccessor?.HttpContext?. - RequestServices.GetService(typeof(CurrentContext)) as CurrentContext; - if(!string.IsNullOrWhiteSpace(currentContext?.DeviceIdentifier)) - { - excludedTokens.Add(currentContext.DeviceIdentifier); - } - - await PushToAllUserDevicesAsync(folder.UserId, type, message, excludedTokens); - } - - 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 PushToAllUserDevicesAsync(userId, type, message, null); - } - - private void InitGcmBroker(GlobalSettings globalSettings) - { - if(string.IsNullOrWhiteSpace(globalSettings.Push.GcmSenderId) || string.IsNullOrWhiteSpace(globalSettings.Push.GcmApiKey) - || string.IsNullOrWhiteSpace(globalSettings.Push.GcmAppPackageName)) - { - return; - } - - var gcmConfig = new GcmConfiguration(globalSettings.Push.GcmSenderId, globalSettings.Push.GcmApiKey, - globalSettings.Push.GcmAppPackageName); - - _gcmBroker = new GcmServiceBroker(gcmConfig); - _gcmBroker.OnNotificationFailed += GcmBroker_OnNotificationFailed; - _gcmBroker.OnNotificationSucceeded += (notification) => - { - Debug.WriteLine("GCM Notification Sent!"); - }; - _gcmBroker.Start(); - } - - private void GcmBroker_OnNotificationFailed(GcmNotification notification, AggregateException exception) - { - exception.Handle(ex => - { - // See what kind of exception it was to further diagnose - if(ex is GcmNotificationException) - { - var notificationException = ex as GcmNotificationException; - - // Deal with the failed notification - var gcmNotification = notificationException.Notification; - var description = notificationException.Description; - - Debug.WriteLine($"GCM Notification Failed: ID={gcmNotification.MessageId}, Desc={description}"); - } - else if(ex is GcmMulticastResultException) - { - var multicastException = ex as GcmMulticastResultException; - - foreach(var succeededNotification in multicastException.Succeeded) - { - Debug.WriteLine($"GCM Notification Failed: ID={succeededNotification.MessageId}"); - } - - foreach(var failedKvp in multicastException.Failed) - { - var n = failedKvp.Key; - var e = failedKvp.Value; - - Debug.WriteLine($"GCM Notification Failed: ID={n.MessageId}, Desc={e.Message}"); - } - - } - else if(ex is DeviceSubscriptionExpiredException) - { - var expiredException = ex as DeviceSubscriptionExpiredException; - - var oldId = expiredException.OldSubscriptionId; - var newId = expiredException.NewSubscriptionId; - - Debug.WriteLine($"Device RegistrationId Expired: {oldId}"); - - if(!string.IsNullOrWhiteSpace(newId)) - { - // If this value isn't null, our subscription changed and we should update our database - Debug.WriteLine($"Device RegistrationId Changed To: {newId}"); - } - } - else if(ex is RetryAfterException) - { - var retryException = (RetryAfterException)ex; - // If you get rate limited, you should stop sending messages until after the RetryAfterUtc date - Debug.WriteLine($"GCM Rate Limited, don't send more until after {retryException.RetryAfterUtc}"); - } - else - { - Debug.WriteLine("GCM Notification Failed for some unknown reason"); - } - - // Mark it as handled - return true; - }); - } - - private void InitApnsBroker(GlobalSettings globalSettings, IHostingEnvironment hostingEnvironment) - { - if(string.IsNullOrWhiteSpace(globalSettings.Push.ApnsCertificatePassword) - || string.IsNullOrWhiteSpace(globalSettings.Push.ApnsCertificateThumbprint)) - { - return; - } - - var apnsCertificate = CoreHelpers.GetCertificate(globalSettings.Push.ApnsCertificateThumbprint); - if(apnsCertificate == null) - { - return; - } - - var apnsConfig = new ApnsConfiguration(hostingEnvironment.IsProduction() ? - ApnsConfiguration.ApnsServerEnvironment.Production : ApnsConfiguration.ApnsServerEnvironment.Sandbox, - apnsCertificate.RawData, globalSettings.Push.ApnsCertificatePassword); - - _apnsBroker = new ApnsServiceBroker(apnsConfig); - _apnsBroker.OnNotificationFailed += ApnsBroker_OnNotificationFailed; - _apnsBroker.OnNotificationSucceeded += (notification) => - { - Debug.WriteLine("Apple Notification Sent!"); - }; - _apnsBroker.Start(); - - var feedbackService = new FeedbackService(apnsConfig); - feedbackService.FeedbackReceived += FeedbackService_FeedbackReceived; - feedbackService.Check(); - } - - private void ApnsBroker_OnNotificationFailed(ApnsNotification notification, AggregateException exception) - { - exception.Handle(ex => - { - // See what kind of exception it was to further diagnose - if(ex is ApnsNotificationException) - { - var notificationException = ex as ApnsNotificationException; - - // Deal with the failed notification - var apnsNotification = notificationException.Notification; - var statusCode = notificationException.ErrorStatusCode; - - Debug.WriteLine($"Apple Notification Failed: ID={apnsNotification.Identifier}, Code={statusCode}"); - } - else - { - // Inner exception might hold more useful information like an ApnsConnectionException - Debug.WriteLine($"Apple Notification Failed for some unknown reason : {ex.InnerException}"); - } - - // Mark it as handled - return true; - }); - } - - private void FeedbackService_FeedbackReceived(string deviceToken, DateTime timestamp) - { - // Remove the deviceToken from your database - // timestamp is the time the token was reported as expired - } - - private async Task PushToAllUserDevicesAsync(Guid userId, PushType type, object message, IEnumerable tokensToSkip) - { - var devices = (await _deviceRepository.GetManyByUserIdAsync(userId)) - .Where(d => !string.IsNullOrWhiteSpace(d.PushToken) && (!tokensToSkip?.Contains(d.PushToken) ?? true)); - if(devices.Count() == 0) - { - return; - } - - if(_apnsBroker != null) - { - var appleNotification = new ApplePayloadPushNotification - { - Data = new PayloadPushNotification.DataObj(type, JsonConvert.SerializeObject(message)) - }; - - var obj = JObject.FromObject(appleNotification); - - // Send to each iOS device - foreach(var device in devices.Where(d => d.Type == DeviceType.iOS)) - { - _apnsBroker.QueueNotification(new ApnsNotification - { - DeviceToken = device.PushToken, - Payload = obj - }); - } - } - - // Android can send to many devices at once - var androidDevices = devices.Where(d => d.Type == DeviceType.Android); - if(_gcmBroker != null && androidDevices.Count() > 0) - { - var gcmData = new PayloadPushNotification.DataObj(type, JsonConvert.SerializeObject(message)); - var obj = JObject.FromObject(gcmData); - - _gcmBroker.QueueNotification(new GcmNotification - { - RegistrationIds = androidDevices.Select(d => d.PushToken).ToList(), - Data = obj - }); - } - } - } -} diff --git a/src/Core/Utilities/CoreHelpers.cs b/src/Core/Utilities/CoreHelpers.cs index a178eec065..4572866ba2 100644 --- a/src/Core/Utilities/CoreHelpers.cs +++ b/src/Core/Utilities/CoreHelpers.cs @@ -5,7 +5,6 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Data; -using System.Linq; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -16,7 +15,7 @@ namespace Bit.Core.Utilities public static class CoreHelpers { private static readonly long _baseDateTicks = new DateTime(1900, 1, 1).Ticks; - private static readonly DateTime _epoc = new DateTime(1970, 1, 1); + private static readonly DateTime _epoc = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); /// /// Generate sequential Guid for Sql Server. diff --git a/src/Core/Utilities/DuoApi.cs b/src/Core/Utilities/DuoApi.cs deleted file mode 100644 index 85f5772241..0000000000 --- a/src/Core/Utilities/DuoApi.cs +++ /dev/null @@ -1,285 +0,0 @@ -/* -Original source modified from https://github.com/duosecurity/duo_api_csharp - -============================================================================= -============================================================================= - -ref: https://github.com/duosecurity/duo_api_csharp/blob/master/LICENSE - -Copyright (c) 2013, Duo Security, Inc. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. -3. The name of the author may not be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR -IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -using System; -using System.Collections.Generic; -using System.Net; -using System.Security.Cryptography; -using System.Text.RegularExpressions; -using System.Text; -using System.Globalization; -using Newtonsoft.Json; -using System.Net.Http; -using System.Threading.Tasks; - -namespace Bit.Core.Utilities.Duo -{ - public class DuoApi - { - public const string DefaultAgent = "Duo.NET, bitwarden"; - - private readonly string _ikey; - private readonly string _skey; - private readonly string _host; - private readonly string _userAgent; - - public DuoApi(string ikey, string skey, string host) - : this(ikey, skey, host, null) - { } - - protected DuoApi(string ikey, string skey, string host, string userAgent) - { - _ikey = ikey; - _skey = skey; - _host = host; - _userAgent = string.IsNullOrWhiteSpace(userAgent) ? DefaultAgent : userAgent; - } - - public async Task> ApiCallAsync(HttpMethod method, string path, - Dictionary parameters, int? timeout = null, DateTime? date = null) - { - var canonParams = CanonicalizeParams(parameters); - var query = string.Empty; - if(method != HttpMethod.Post && method != HttpMethod.Put && parameters.Count > 0) - { - query = "?" + canonParams; - } - - var url = $"https://{_host}{path}{query}"; - - var dateString = DateToRFC822(date.GetValueOrDefault(DateTime.UtcNow)); - var auth = Sign(method.ToString(), path, canonParams, dateString); - - var client = new HttpClient(); - client.DefaultRequestHeaders.Add("Accept", "application/json"); - client.DefaultRequestHeaders.Add("Authorization", auth); - client.DefaultRequestHeaders.Add("X-Duo-Date", dateString); - client.DefaultRequestHeaders.Add("User-Agent", _userAgent); - - if(timeout.GetValueOrDefault(0) > 0) - { - client.Timeout = new TimeSpan(0, 0, 0, 0, timeout.Value); - } - - var request = new HttpRequestMessage - { - RequestUri = new Uri(url), - Method = method - }; - - if(method == HttpMethod.Post || method == HttpMethod.Put) - { - request.Content = new FormUrlEncodedContent(parameters); - } - - HttpResponseMessage response = null; - try - { - response = await client.SendAsync(request); - } - catch(WebException) - { - if(response?.Content == null) - { - throw; - } - } - - var result = await response.Content.ReadAsStringAsync(); - return new Tuple(result, response.StatusCode); - } - - public async Task JSONApiCallAsync(HttpMethod method, string path, Dictionary parameters, - int? timeout = null, DateTime? date = null) where T : class - { - var resTuple = await ApiCallAsync(method, path, parameters, timeout, date); - var res = resTuple.Item1; - HttpStatusCode statusCode = resTuple.Item2; - - try - { - var resDict = JsonConvert.DeserializeObject>(res); - var stat = resDict["stat"] as string; - if(stat == "OK") - { - return JsonConvert.DeserializeObject(resDict["response"].ToString()); - } - else - { - var code = resDict["code"] as int?; - var message = resDict["message"] as string; - - var messageDetail = string.Empty; - if(resDict.ContainsKey("message_detail")) - { - messageDetail = resDict["message_detail"] as string; - } - - throw new DuoApiException(code.GetValueOrDefault(0), statusCode, message, messageDetail); - } - } - catch(Exception e) - { - throw new DuoBadResponseException(statusCode, e); - } - } - - private string CanonicalizeParams(Dictionary parameters) - { - var ret = new List(); - foreach(var pair in parameters) - { - var p = $"{WebUtility.UrlEncode(pair.Key)}={WebUtility.UrlEncode(pair.Value)}"; - // Signatures require upper-case hex digits. - p = Regex.Replace(p, "(%[0-9A-Fa-f][0-9A-Fa-f])", c => c.Value.ToUpperInvariant()); - // Escape only the expected characters. - p = Regex.Replace(p, "([!'()*])", c => "%" + Convert.ToByte(c.Value[0]).ToString("X")); - p = p.Replace("%7E", "~"); - // UrlEncode converts space (" ") to "+". The - // signature algorithm requires "%20" instead. Actual - // + has already been replaced with %2B. - p = p.Replace("+", "%20"); - - ret.Add(p); - } - - ret.Sort(StringComparer.Ordinal); - return string.Join("&", ret.ToArray()); - } - - private string CanonicalizeRequest(string method, string path, string canon_params, string date) - { - string[] lines = { date, method.ToUpperInvariant(), _host.ToLower(), path, canon_params }; - return string.Join("\n", lines); - } - - private string Sign(string method, string path, string canon_params, string date) - { - var canon = CanonicalizeRequest(method, path, canon_params, date); - var sig = HmacSign(canon); - var auth = $"{_ikey }:{sig}"; - var authBytes = Encoding.ASCII.GetBytes(auth); - return $"Basic {Convert.ToBase64String(authBytes)}"; - } - - private string HmacSign(string data) - { - var keyBytes = Encoding.ASCII.GetBytes(_skey); - var dataBytes = Encoding.ASCII.GetBytes(data); - - using(var hmac = new HMACSHA1(keyBytes)) - { - var hash = hmac.ComputeHash(dataBytes); - var hex = BitConverter.ToString(hash); - return hex.Replace("-", "").ToLower(); - } - } - - private string DateToRFC822(DateTime date) - { - // Can't use the "zzzz" format because it adds a ":" - // between the offset's hours and minutes. - var dateString = date.ToString("ddd, dd MMM yyyy HH:mm:ss", CultureInfo.InvariantCulture); - - // TODO: Get proper timezone offset. hardcoded to UTC for now. - var offset = 0; - - string zone; - // + or -, then 0-pad, then offset, then more 0-padding. - if(offset < 0) - { - offset *= -1; - zone = "-"; - } - else - { - zone = "+"; - } - - zone += offset.ToString(CultureInfo.InvariantCulture).PadLeft(2, '0'); - dateString += (" " + zone.PadRight(5, '0')); - return dateString; - } - } - - public class DuoException : Exception - { - public HttpStatusCode Status { get; private set; } - - public DuoException(HttpStatusCode status, string message, Exception inner) - : base(message, inner) - { - Status = status; - } - } - - public class DuoApiException : DuoException - { - public int Code { get; private set; } - public string ApiMessage { get; private set; } - public string ApiMessageDetail { get; private set; } - - public DuoApiException(int code, HttpStatusCode status, string message, string messageDetail) - : base(status, FormatMessage(code, message, messageDetail), null) - { - Code = code; - ApiMessage = message; - ApiMessageDetail = messageDetail; - } - - private static string FormatMessage(int code, string message, string messageDetail) - { - return $"Duo API Error {code}: '{message}' ('{messageDetail}')."; - } - } - - public class DuoBadResponseException : DuoException - { - public DuoBadResponseException(HttpStatusCode status, Exception inner) - : base(status, FormatMessage(status, inner), inner) - { } - - private static string FormatMessage(HttpStatusCode status, Exception inner) - { - var innerMessage = "(null)"; - if(inner != null) - { - innerMessage = string.Format("'{0}'", inner.Message); - } - - return $"Got error '{innerMessage}' with HTTP status {(int)status}."; - } - } -} diff --git a/src/Identity/settings.json b/src/Identity/settings.json index 1dae623b37..7e013645d9 100644 --- a/src/Identity/settings.json +++ b/src/Identity/settings.json @@ -2,7 +2,6 @@ "globalSettings": { "siteName": "bitwarden", "baseVaultUri": "http://localhost:4001/#", - "jwtSigningKey": "THIS IS A SECRET. IT KEEPS YOUR TOKEN SAFE. :)", "stripeApiKey": "SECRET", "sqlServer": { "connectionString": "SECRET" @@ -11,13 +10,6 @@ "apiKey": "SECRET", "replyToEmail": "hello@bitwarden.com" }, - "push": { - "apnsCertificateThumbprint": "SECRET", - "apnsCertificatePassword": "SECRET", - "gcmSenderId": "SECRET", - "gcmApiKey": "SECRET", - "gcmAppPackageName": "com.x8bit.bitwarden" - }, "identityServer": { "certificateThumbprint": "SECRET" },