diff --git a/src/Api/Controllers/AccountsController.cs b/src/Api/Controllers/AccountsController.cs index 4318d21cc5..3c5a72eefa 100644 --- a/src/Api/Controllers/AccountsController.cs +++ b/src/Api/Controllers/AccountsController.cs @@ -157,6 +157,13 @@ namespace Bit.Api.Controllers return Task.FromResult(response); } + [HttpGet("domains")] + public Task GetDomains() + { + var response = new DomainsResponseModel(_currentContext.User); + return Task.FromResult(response); + } + [HttpPut("profile")] [HttpPost("profile")] public async Task PutProfile([FromBody]UpdateProfileRequestModel model) @@ -165,10 +172,20 @@ namespace Bit.Api.Controllers var response = new ProfileResponseModel(_currentContext.User); return response; + } + + [HttpPut("domains")] + [HttpPost("domains")] + public async Task PutDomains([FromBody]UpdateDomainsRequestModel model) + { + await _userService.SaveUserAsync(model.ToUser(_currentContext.User)); + + var response = new DomainsResponseModel(_currentContext.User); + return response; } [HttpGet("two-factor")] - public async Task GetTwoFactor(string masterPasswordHash, TwoFactorProvider provider) + public async Task GetTwoFactor(string masterPasswordHash, TwoFactorProviderType provider) { var user = _currentContext.User; if(!await _userManager.CheckPasswordAsync(user, masterPasswordHash)) @@ -200,7 +217,7 @@ namespace Bit.Api.Controllers throw new BadRequestException("Token", "Invalid token."); } - user.TwoFactorProvider = TwoFactorProvider.Authenticator; + user.TwoFactorProvider = TwoFactorProviderType.Authenticator; user.TwoFactorEnabled = model.Enabled.Value; user.TwoFactorRecoveryCode = user.TwoFactorEnabled ? Guid.NewGuid().ToString("N") : null; await _userService.SaveUserAsync(user); diff --git a/src/Api/Controllers/LoginsController.cs b/src/Api/Controllers/LoginsController.cs index d60a7a95c1..6b2fe153ed 100644 --- a/src/Api/Controllers/LoginsController.cs +++ b/src/Api/Controllers/LoginsController.cs @@ -14,7 +14,7 @@ using Bit.Core.Services; namespace Bit.Api.Controllers { [Route("logins")] - // sites route is deprecated + // "sites" route is deprecated [Route("sites")] [Authorize("Application")] public class LoginsController : Controller diff --git a/src/Api/Models/Request/Accounts/UpdateDomainsRequestModel.cs b/src/Api/Models/Request/Accounts/UpdateDomainsRequestModel.cs new file mode 100644 index 0000000000..68f19c6d26 --- /dev/null +++ b/src/Api/Models/Request/Accounts/UpdateDomainsRequestModel.cs @@ -0,0 +1,21 @@ +using Bit.Core.Domains; +using System.Collections.Generic; +using Bit.Core.Enums; +using Newtonsoft.Json; + +namespace Bit.Api.Models +{ + public class UpdateDomainsRequestModel + { + public IEnumerable> EquivalentDomains { get; set; } + public IEnumerable ExcludedGlobalEquivalentDomains { get; set; } + + public User ToUser(User existingUser) + { + existingUser.EquivalentDomains = EquivalentDomains != null ? JsonConvert.SerializeObject(EquivalentDomains) : null; + existingUser.ExcludedGlobalEquivalentDomains = ExcludedGlobalEquivalentDomains != null ? + JsonConvert.SerializeObject(ExcludedGlobalEquivalentDomains) : null; + return existingUser; + } + } +} diff --git a/src/Api/Models/Response/DomainsResponseModel.cs b/src/Api/Models/Response/DomainsResponseModel.cs new file mode 100644 index 0000000000..941cd91e75 --- /dev/null +++ b/src/Api/Models/Response/DomainsResponseModel.cs @@ -0,0 +1,30 @@ +using System; +using Bit.Core.Domains; +using System.Collections.Generic; +using Newtonsoft.Json; +using Bit.Core.Enums; + +namespace Bit.Api.Models +{ + public class DomainsResponseModel : ResponseModel + { + public DomainsResponseModel(User user) + : base("domains") + { + if(user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + EquivalentDomains = user.EquivalentDomains != null ? + JsonConvert.DeserializeObject>>(user.EquivalentDomains) : null; + GlobalEquivalentDomains = Core.Utilities.EquivalentDomains.Global; + ExcludedGlobalEquivalentDomains = user.ExcludedGlobalEquivalentDomains != null ? + JsonConvert.DeserializeObject>(user.ExcludedGlobalEquivalentDomains) : null; + } + + public IEnumerable> EquivalentDomains { get; set; } + public IDictionary> GlobalEquivalentDomains { get; set; } + public IEnumerable ExcludedGlobalEquivalentDomains { get; set; } + } +} diff --git a/src/Api/Models/Response/TwoFactorResponseModel.cs b/src/Api/Models/Response/TwoFactorResponseModel.cs index f2db89c3a1..da598f1cc6 100644 --- a/src/Api/Models/Response/TwoFactorResponseModel.cs +++ b/src/Api/Models/Response/TwoFactorResponseModel.cs @@ -21,7 +21,7 @@ namespace Bit.Api.Models } public bool TwoFactorEnabled { get; set; } - public TwoFactorProvider? TwoFactorProvider { get; set; } + public TwoFactorProviderType? TwoFactorProvider { get; set; } public string AuthenticatorKey { get; set; } public string TwoFactorRecoveryCode { get; set; } } diff --git a/src/Core/Domains/User.cs b/src/Core/Domains/User.cs index fa627fcfbb..cf79a7929a 100644 --- a/src/Core/Domains/User.cs +++ b/src/Core/Domains/User.cs @@ -15,9 +15,11 @@ namespace Bit.Core.Domains public string Culture { get; set; } = "en-US"; public string SecurityStamp { get; set; } public bool TwoFactorEnabled { get; set; } - public TwoFactorProvider? TwoFactorProvider { get; set; } + public TwoFactorProviderType? TwoFactorProvider { get; set; } public string AuthenticatorKey { get; set; } public string TwoFactorRecoveryCode { get; set; } + public string EquivalentDomains { get; set; } + public string ExcludedGlobalEquivalentDomains { get; set; } public DateTime CreationDate { get; internal set; } = DateTime.UtcNow; public DateTime RevisionDate { get; internal set; } = DateTime.UtcNow; diff --git a/src/Core/Enums/CipherType.cs b/src/Core/Enums/CipherType.cs index e9df41f34b..9f56e8d01b 100644 --- a/src/Core/Enums/CipherType.cs +++ b/src/Core/Enums/CipherType.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Enums { - public enum CipherType : short + public enum CipherType : byte { Folder = 0, Login = 1 diff --git a/src/Core/Enums/DeviceType.cs b/src/Core/Enums/DeviceType.cs index 1d95efef4c..3b0c3d9d8c 100644 --- a/src/Core/Enums/DeviceType.cs +++ b/src/Core/Enums/DeviceType.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Enums { - public enum DeviceType : short + public enum DeviceType : byte { Android = 0, iOS = 1 diff --git a/src/Core/Enums/GlobalEquivalentDomainsType.cs b/src/Core/Enums/GlobalEquivalentDomainsType.cs new file mode 100644 index 0000000000..7cc4fd724c --- /dev/null +++ b/src/Core/Enums/GlobalEquivalentDomainsType.cs @@ -0,0 +1,8 @@ +namespace Bit.Core.Enums +{ + public enum GlobalEquivalentDomainsType : byte + { + Google = 0, + Apple = 1 + } +} diff --git a/src/Core/Enums/PushType.cs b/src/Core/Enums/PushType.cs index 27544f4e08..e919e92006 100644 --- a/src/Core/Enums/PushType.cs +++ b/src/Core/Enums/PushType.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Enums { - public enum PushType : short + public enum PushType : byte { SyncCipherUpdate = 0, SyncCipherCreate = 1, diff --git a/src/Core/Enums/TwoFactorProvider.cs b/src/Core/Enums/TwoFactorProviderType.cs similarity index 60% rename from src/Core/Enums/TwoFactorProvider.cs rename to src/Core/Enums/TwoFactorProviderType.cs index 2b9eb0e861..86ad53ac00 100644 --- a/src/Core/Enums/TwoFactorProvider.cs +++ b/src/Core/Enums/TwoFactorProviderType.cs @@ -1,6 +1,6 @@ namespace Bit.Core.Enums { - public enum TwoFactorProvider + public enum TwoFactorProviderType : byte { Authenticator = 0 } diff --git a/src/Core/Identity/AuthenticatorTokenProvider.cs b/src/Core/Identity/AuthenticatorTokenProvider.cs index 09a4796e5f..dbbb1ce4ab 100644 --- a/src/Core/Identity/AuthenticatorTokenProvider.cs +++ b/src/Core/Identity/AuthenticatorTokenProvider.cs @@ -14,7 +14,7 @@ namespace Bit.Core.Identity { var canGenerate = user.TwoFactorEnabled && user.TwoFactorProvider.HasValue - && user.TwoFactorProvider.Value == TwoFactorProvider.Authenticator + && user.TwoFactorProvider.Value == TwoFactorProviderType.Authenticator && !string.IsNullOrWhiteSpace(user.AuthenticatorKey); return Task.FromResult(canGenerate); diff --git a/src/Core/Services/IUserService.cs b/src/Core/Services/IUserService.cs index d277fb60e8..52196fda5e 100644 --- a/src/Core/Services/IUserService.cs +++ b/src/Core/Services/IUserService.cs @@ -16,7 +16,7 @@ namespace Bit.Core.Services Task ChangeEmailAsync(User user, string masterPassword, string newEmail, string newMasterPassword, string token, IEnumerable ciphers); Task ChangePasswordAsync(User user, string currentMasterPasswordHash, string newMasterPasswordHash, IEnumerable ciphers); Task RefreshSecurityStampAsync(User user, string masterPasswordHash); - Task GetTwoFactorAsync(User user, Enums.TwoFactorProvider provider); + Task GetTwoFactorAsync(User user, Enums.TwoFactorProviderType provider); Task RecoverTwoFactorAsync(string email, string masterPassword, string recoveryCode); Task DeleteAsync(User user); } diff --git a/src/Core/Services/Implementations/UserService.cs b/src/Core/Services/Implementations/UserService.cs index f5195e61db..549bef4e00 100644 --- a/src/Core/Services/Implementations/UserService.cs +++ b/src/Core/Services/Implementations/UserService.cs @@ -216,13 +216,13 @@ namespace Bit.Core.Services return IdentityResult.Failed(_identityErrorDescriber.PasswordMismatch()); } - public async Task GetTwoFactorAsync(User user, Enums.TwoFactorProvider provider) + public async Task GetTwoFactorAsync(User user, Enums.TwoFactorProviderType provider) { if(user.TwoFactorEnabled && user.TwoFactorProvider.HasValue && user.TwoFactorProvider.Value == provider) { switch(provider) { - case Enums.TwoFactorProvider.Authenticator: + case Enums.TwoFactorProviderType.Authenticator: if(!string.IsNullOrWhiteSpace(user.AuthenticatorKey)) { return; @@ -239,7 +239,7 @@ namespace Bit.Core.Services switch(provider) { - case Enums.TwoFactorProvider.Authenticator: + case Enums.TwoFactorProviderType.Authenticator: var key = KeyGeneration.GenerateRandomKey(20); user.AuthenticatorKey = Base32Encoder.Encode(key); break; @@ -269,7 +269,7 @@ namespace Bit.Core.Services return false; } - user.TwoFactorProvider = TwoFactorProvider.Authenticator; + user.TwoFactorProvider = TwoFactorProviderType.Authenticator; user.TwoFactorEnabled = false; user.TwoFactorRecoveryCode = null; await SaveUserAsync(user); diff --git a/src/Core/Utilities/EquivalentDomains.cs b/src/Core/Utilities/EquivalentDomains.cs new file mode 100644 index 0000000000..d60ac6ae91 --- /dev/null +++ b/src/Core/Utilities/EquivalentDomains.cs @@ -0,0 +1,19 @@ +using Bit.Core.Enums; +using System.Collections.Generic; + +namespace Bit.Core.Utilities +{ + public class EquivalentDomains + { + static EquivalentDomains() + { + Global = new Dictionary>(); + + Global.Add(GlobalEquivalentDomainsType.Apple, new List() { "apple.com", "icloud.com" }); + Global.Add(GlobalEquivalentDomainsType.Google, new List { "google.com", "youtube.com", "gmail.com" }); + } + + public static IDictionary> Global { get; set; } + + } +} diff --git a/src/Sql/dbo/Stored Procedures/User_Create.sql b/src/Sql/dbo/Stored Procedures/User_Create.sql index cd7d32e081..8cedfc0cb4 100644 --- a/src/Sql/dbo/Stored Procedures/User_Create.sql +++ b/src/Sql/dbo/Stored Procedures/User_Create.sql @@ -11,6 +11,8 @@ @TwoFactorProvider TINYINT, @AuthenticatorKey NVARCHAR(50), @TwoFactorRecoveryCode NVARCHAR(32), + @EquivalentDomains NVARCHAR(MAX), + @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) AS @@ -31,6 +33,8 @@ BEGIN [TwoFactorProvider], [AuthenticatorKey], [TwoFactorRecoveryCode], + [EquivalentDomains], + [ExcludedGlobalEquivalentDomains], [CreationDate], [RevisionDate] ) @@ -48,6 +52,8 @@ BEGIN @TwoFactorProvider, @AuthenticatorKey, @TwoFactorRecoveryCode, + @EquivalentDomains, + @ExcludedGlobalEquivalentDomains, @CreationDate, @RevisionDate ) diff --git a/src/Sql/dbo/Stored Procedures/User_Update.sql b/src/Sql/dbo/Stored Procedures/User_Update.sql index ce53d82df8..8f895b3087 100644 --- a/src/Sql/dbo/Stored Procedures/User_Update.sql +++ b/src/Sql/dbo/Stored Procedures/User_Update.sql @@ -11,6 +11,8 @@ @TwoFactorProvider TINYINT, @AuthenticatorKey NVARCHAR(50), @TwoFactorRecoveryCode NVARCHAR(32), + @EquivalentDomains NVARCHAR(MAX), + @ExcludedGlobalEquivalentDomains NVARCHAR(MAX), @CreationDate DATETIME2(7), @RevisionDate DATETIME2(7) AS @@ -31,6 +33,8 @@ BEGIN [TwoFactorProvider] = @TwoFactorProvider, [AuthenticatorKey] = @AuthenticatorKey, [TwoFactorRecoveryCode] = @TwoFactorRecoveryCode, + [EquivalentDomains] = @EquivalentDomains, + [ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains, [CreationDate] = @CreationDate, [RevisionDate] = @RevisionDate WHERE diff --git a/src/Sql/dbo/Tables/User.sql b/src/Sql/dbo/Tables/User.sql index 08a8684a57..6719b71d69 100644 --- a/src/Sql/dbo/Tables/User.sql +++ b/src/Sql/dbo/Tables/User.sql @@ -1,18 +1,20 @@ CREATE TABLE [dbo].[User] ( - [Id] UNIQUEIDENTIFIER NOT NULL, - [Name] NVARCHAR (50) NULL, - [Email] NVARCHAR (50) NOT NULL, - [EmailVerified] BIT NOT NULL, - [MasterPassword] NVARCHAR (300) NOT NULL, - [MasterPasswordHint] NVARCHAR (50) NULL, - [Culture] NVARCHAR (10) NOT NULL, - [SecurityStamp] NVARCHAR (50) NOT NULL, - [TwoFactorEnabled] BIT NOT NULL, - [TwoFactorProvider] TINYINT NULL, - [AuthenticatorKey] NVARCHAR (50) NULL, - [TwoFactorRecoveryCode] NVARCHAR (32) NULL, - [CreationDate] DATETIME2 (7) NOT NULL, - [RevisionDate] DATETIME2 (7) NOT NULL, + [Id] UNIQUEIDENTIFIER NOT NULL, + [Name] NVARCHAR (50) NULL, + [Email] NVARCHAR (50) NOT NULL, + [EmailVerified] BIT NOT NULL, + [MasterPassword] NVARCHAR (300) NOT NULL, + [MasterPasswordHint] NVARCHAR (50) NULL, + [Culture] NVARCHAR (10) NOT NULL, + [SecurityStamp] NVARCHAR (50) NOT NULL, + [TwoFactorEnabled] BIT NOT NULL, + [TwoFactorProvider] TINYINT NULL, + [AuthenticatorKey] NVARCHAR (50) NULL, + [TwoFactorRecoveryCode] NVARCHAR (32) NULL, + [EquivalentDomains] NVARCHAR (MAX) NULL, + [ExcludedGlobalEquivalentDomains] NVARCHAR (MAX) NULL, + [CreationDate] DATETIME2 (7) NOT NULL, + [RevisionDate] DATETIME2 (7) NOT NULL, CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC) );