mirror of
https://github.com/bitwarden/server.git
synced 2025-02-16 01:51:21 +01:00
[PS-589] Fix emergency contact takeover device verification and endpoints for its settings (#2016)
* Added UnknownDeviceVerificationEnabled on User that is turned off when emergency contact takes over the account. Also added endpoints to get and update 2fa device verification settings. And Updated migrations & tests * Applied dotnet format * Fixed method rename call on TwoFactorController * PS-589 Format fixes * PS-589 changed UnknownDeviceVerificationEnabled to be non-nullable
This commit is contained in:
parent
16c6b23a27
commit
b070e9a387
@ -380,6 +380,42 @@ namespace Bit.Api.Controllers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpGet("get-device-verification-settings")]
|
||||||
|
public async Task<DeviceVerificationResponseModel> GetDeviceVerificationSettings()
|
||||||
|
{
|
||||||
|
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (User.Claims.HasSsoIdP())
|
||||||
|
{
|
||||||
|
return new DeviceVerificationResponseModel(false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new DeviceVerificationResponseModel(_userService.CanEditDeviceVerificationSettings(user), user.UnknownDeviceVerificationEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("device-verification-settings")]
|
||||||
|
public async Task<DeviceVerificationResponseModel> PutDeviceVerificationSettings([FromBody] DeviceVerificationRequestModel model)
|
||||||
|
{
|
||||||
|
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||||
|
if (user == null)
|
||||||
|
{
|
||||||
|
throw new UnauthorizedAccessException();
|
||||||
|
}
|
||||||
|
if (!_userService.CanEditDeviceVerificationSettings(user)
|
||||||
|
|| User.Claims.HasSsoIdP())
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Can't update device verification settings");
|
||||||
|
}
|
||||||
|
|
||||||
|
model.ToUser(user);
|
||||||
|
await _userService.SaveUserAsync(user);
|
||||||
|
return new DeviceVerificationResponseModel(true, user.UnknownDeviceVerificationEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<User> CheckAsync(SecretVerificationRequestModel model, bool premium)
|
private async Task<User> CheckAsync(SecretVerificationRequestModel model, bool premium)
|
||||||
{
|
{
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||||
|
18
src/Api/Models/Request/DeviceVerificationRequestModel.cs
Normal file
18
src/Api/Models/Request/DeviceVerificationRequestModel.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Bit.Core.Entities;
|
||||||
|
|
||||||
|
namespace Bit.Api.Models.Request
|
||||||
|
{
|
||||||
|
public class DeviceVerificationRequestModel
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public bool UnknownDeviceVerificationEnabled { get; set; }
|
||||||
|
|
||||||
|
public User ToUser(User user)
|
||||||
|
{
|
||||||
|
user.UnknownDeviceVerificationEnabled = UnknownDeviceVerificationEnabled;
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/Api/Models/Response/DeviceVerificationResponseModel.cs
Normal file
18
src/Api/Models/Response/DeviceVerificationResponseModel.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using Bit.Core.Models.Api;
|
||||||
|
|
||||||
|
namespace Bit.Api.Models.Response
|
||||||
|
{
|
||||||
|
public class DeviceVerificationResponseModel : ResponseModel
|
||||||
|
{
|
||||||
|
public DeviceVerificationResponseModel(bool isDeviceVerificationSectionEnabled, bool unknownDeviceVerificationEnabled)
|
||||||
|
: base("deviceVerificationSettings")
|
||||||
|
{
|
||||||
|
IsDeviceVerificationSectionEnabled = isDeviceVerificationSectionEnabled;
|
||||||
|
UnknownDeviceVerificationEnabled = unknownDeviceVerificationEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDeviceVerificationSectionEnabled { get; }
|
||||||
|
public bool UnknownDeviceVerificationEnabled { get; }
|
||||||
|
}
|
||||||
|
}
|
@ -62,6 +62,7 @@ namespace Bit.Core.Entities
|
|||||||
public bool UsesKeyConnector { get; set; }
|
public bool UsesKeyConnector { get; set; }
|
||||||
public int FailedLoginCount { get; set; }
|
public int FailedLoginCount { get; set; }
|
||||||
public DateTime? LastFailedLoginDate { get; set; }
|
public DateTime? LastFailedLoginDate { get; set; }
|
||||||
|
public bool UnknownDeviceVerificationEnabled { get; set; }
|
||||||
|
|
||||||
public void SetNewId()
|
public void SetNewId()
|
||||||
{
|
{
|
||||||
|
@ -79,5 +79,6 @@ namespace Bit.Core.Services
|
|||||||
Task<bool> VerifyOTPAsync(User user, string token);
|
Task<bool> VerifyOTPAsync(User user, string token);
|
||||||
Task<bool> VerifySecretAsync(User user, string secret);
|
Task<bool> VerifySecretAsync(User user, string secret);
|
||||||
Task<bool> Needs2FABecauseNewDeviceAsync(User user, string deviceIdentifier, string grantType);
|
Task<bool> Needs2FABecauseNewDeviceAsync(User user, string deviceIdentifier, string grantType);
|
||||||
|
bool CanEditDeviceVerificationSettings(User user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -327,6 +327,7 @@ namespace Bit.Core.Services
|
|||||||
grantor.Key = key;
|
grantor.Key = key;
|
||||||
// Disable TwoFactor providers since they will otherwise block logins
|
// Disable TwoFactor providers since they will otherwise block logins
|
||||||
grantor.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>());
|
grantor.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>());
|
||||||
|
grantor.UnknownDeviceVerificationEnabled = false;
|
||||||
await _userRepository.ReplaceAsync(grantor);
|
await _userRepository.ReplaceAsync(grantor);
|
||||||
|
|
||||||
// Remove grantor from all organizations unless Owner
|
// Remove grantor from all organizations unless Owner
|
||||||
|
@ -1417,12 +1417,20 @@ namespace Bit.Core.Services
|
|||||||
|
|
||||||
public async Task<bool> Needs2FABecauseNewDeviceAsync(User user, string deviceIdentifier, string grantType)
|
public async Task<bool> Needs2FABecauseNewDeviceAsync(User user, string deviceIdentifier, string grantType)
|
||||||
{
|
{
|
||||||
return _globalSettings.TwoFactorAuth.EmailOnNewDeviceLogin
|
return CanEditDeviceVerificationSettings(user)
|
||||||
&& user.EmailVerified
|
&& user.UnknownDeviceVerificationEnabled
|
||||||
&& grantType != "authorization_code"
|
&& grantType != "authorization_code"
|
||||||
&& await IsNewDeviceAndNotTheFirstOneAsync(user, deviceIdentifier);
|
&& await IsNewDeviceAndNotTheFirstOneAsync(user, deviceIdentifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanEditDeviceVerificationSettings(User user)
|
||||||
|
{
|
||||||
|
return _globalSettings.TwoFactorAuth.EmailOnNewDeviceLogin
|
||||||
|
&& user.EmailVerified
|
||||||
|
&& !user.UsesKeyConnector
|
||||||
|
&& !(user.GetTwoFactorProviders()?.Any() ?? false);
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<bool> IsNewDeviceAndNotTheFirstOneAsync(User user, string deviceIdentifier)
|
private async Task<bool> IsNewDeviceAndNotTheFirstOneAsync(User user, string deviceIdentifier)
|
||||||
{
|
{
|
||||||
if (user == null)
|
if (user == null)
|
||||||
|
15
src/Core/Utilities/ClaimsExtensions.cs
Normal file
15
src/Core/Utilities/ClaimsExtensions.cs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Security.Claims;
|
||||||
|
|
||||||
|
namespace Bit.Core.Utilities
|
||||||
|
{
|
||||||
|
public static class ClaimsExtensions
|
||||||
|
{
|
||||||
|
public static bool HasSsoIdP(this IEnumerable<Claim> claims)
|
||||||
|
{
|
||||||
|
return claims.Any(c => c.Type == "idp" && c.Value == "sso");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -33,7 +33,8 @@
|
|||||||
@ForcePasswordReset BIT = 0,
|
@ForcePasswordReset BIT = 0,
|
||||||
@UsesKeyConnector BIT = 0,
|
@UsesKeyConnector BIT = 0,
|
||||||
@FailedLoginCount INT = 0,
|
@FailedLoginCount INT = 0,
|
||||||
@LastFailedLoginDate DATETIME2(7)
|
@LastFailedLoginDate DATETIME2(7),
|
||||||
|
@UnknownDeviceVerificationEnabled BIT NULL
|
||||||
AS
|
AS
|
||||||
BEGIN
|
BEGIN
|
||||||
SET NOCOUNT ON
|
SET NOCOUNT ON
|
||||||
@ -74,7 +75,8 @@ BEGIN
|
|||||||
[ForcePasswordReset],
|
[ForcePasswordReset],
|
||||||
[UsesKeyConnector],
|
[UsesKeyConnector],
|
||||||
[FailedLoginCount],
|
[FailedLoginCount],
|
||||||
[LastFailedLoginDate]
|
[LastFailedLoginDate],
|
||||||
|
[UnknownDeviceVerificationEnabled]
|
||||||
)
|
)
|
||||||
VALUES
|
VALUES
|
||||||
(
|
(
|
||||||
@ -112,6 +114,7 @@ BEGIN
|
|||||||
@ForcePasswordReset,
|
@ForcePasswordReset,
|
||||||
@UsesKeyConnector,
|
@UsesKeyConnector,
|
||||||
@FailedLoginCount,
|
@FailedLoginCount,
|
||||||
@LastFailedLoginDate
|
@LastFailedLoginDate,
|
||||||
|
@UnknownDeviceVerificationEnabled
|
||||||
)
|
)
|
||||||
END
|
END
|
||||||
|
@ -33,7 +33,8 @@
|
|||||||
@ForcePasswordReset BIT = 0,
|
@ForcePasswordReset BIT = 0,
|
||||||
@UsesKeyConnector BIT = 0,
|
@UsesKeyConnector BIT = 0,
|
||||||
@FailedLoginCount INT,
|
@FailedLoginCount INT,
|
||||||
@LastFailedLoginDate DATETIME2(7)
|
@LastFailedLoginDate DATETIME2(7),
|
||||||
|
@UnknownDeviceVerificationEnabled BIT NULL
|
||||||
AS
|
AS
|
||||||
BEGIN
|
BEGIN
|
||||||
SET NOCOUNT ON
|
SET NOCOUNT ON
|
||||||
@ -74,7 +75,8 @@ BEGIN
|
|||||||
[ForcePasswordReset] = @ForcePasswordReset,
|
[ForcePasswordReset] = @ForcePasswordReset,
|
||||||
[UsesKeyConnector] = @UsesKeyConnector,
|
[UsesKeyConnector] = @UsesKeyConnector,
|
||||||
[FailedLoginCount] = @FailedLoginCount,
|
[FailedLoginCount] = @FailedLoginCount,
|
||||||
[LastFailedLoginDate] = @LastFailedLoginDate
|
[LastFailedLoginDate] = @LastFailedLoginDate,
|
||||||
|
[UnknownDeviceVerificationEnabled] = @UnknownDeviceVerificationEnabled
|
||||||
WHERE
|
WHERE
|
||||||
[Id] = @Id
|
[Id] = @Id
|
||||||
END
|
END
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
[UsesKeyConnector] BIT NOT NULL,
|
[UsesKeyConnector] BIT NOT NULL,
|
||||||
[FailedLoginCount] INT NOT NULL,
|
[FailedLoginCount] INT NOT NULL,
|
||||||
[LastFailedLoginDate] DATETIME2 (7) NULL,
|
[LastFailedLoginDate] DATETIME2 (7) NULL,
|
||||||
|
[UnknownDeviceVerificationEnabled] BIT NULL,
|
||||||
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC)
|
CONSTRAINT [PK_User] PRIMARY KEY CLUSTERED ([Id] ASC)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Bit.Core.Entities;
|
using Bit.Core.Entities;
|
||||||
|
using Bit.Core.Enums;
|
||||||
using Bit.Core.Exceptions;
|
using Bit.Core.Exceptions;
|
||||||
|
using Bit.Core.Models;
|
||||||
using Bit.Core.Repositories;
|
using Bit.Core.Repositories;
|
||||||
using Bit.Core.Services;
|
using Bit.Core.Services;
|
||||||
using Bit.Core.Test.AutoFixture;
|
using Bit.Core.Test.AutoFixture;
|
||||||
@ -137,5 +140,37 @@ namespace Bit.Core.Test.Services
|
|||||||
|
|
||||||
Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message);
|
Assert.Contains("You cannot takeover an account that is using Key Connector", exception.Message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
|
public async Task PasswordAsync_Disables_2FA_Providers_And_Unknown_Device_Verification_On_The_Grantor(
|
||||||
|
SutProvider<EmergencyAccessService> sutProvider, User requestingUser, User grantor)
|
||||||
|
{
|
||||||
|
grantor.UsesKeyConnector = true;
|
||||||
|
grantor.UnknownDeviceVerificationEnabled = true;
|
||||||
|
grantor.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||||
|
{
|
||||||
|
[TwoFactorProviderType.Email] = new TwoFactorProvider
|
||||||
|
{
|
||||||
|
MetaData = new Dictionary<string, object> { ["Email"] = "asdfasf" },
|
||||||
|
Enabled = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var emergencyAccess = new EmergencyAccess
|
||||||
|
{
|
||||||
|
GrantorId = grantor.Id,
|
||||||
|
GranteeId = requestingUser.Id,
|
||||||
|
Status = Enums.EmergencyAccessStatusType.RecoveryApproved,
|
||||||
|
Type = Enums.EmergencyAccessType.Takeover,
|
||||||
|
};
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IEmergencyAccessRepository>().GetByIdAsync(Arg.Any<Guid>()).Returns(emergencyAccess);
|
||||||
|
sutProvider.GetDependency<IUserRepository>().GetByIdAsync(grantor.Id).Returns(grantor);
|
||||||
|
|
||||||
|
await sutProvider.Sut.PasswordAsync(Guid.NewGuid(), requestingUser, "blablahash", "blablakey");
|
||||||
|
|
||||||
|
Assert.False(grantor.UnknownDeviceVerificationEnabled);
|
||||||
|
Assert.Empty(grantor.GetTwoFactorProviders());
|
||||||
|
await sutProvider.GetDependency<IUserRepository>().Received().ReplaceAsync(grantor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,8 +161,9 @@ namespace Bit.Core.Test.Services
|
|||||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
public async Task Needs2FABecauseNewDeviceAsync_ReturnsTrue(SutProvider<UserService> sutProvider, User user)
|
public async Task Needs2FABecauseNewDeviceAsync_ReturnsTrue(SutProvider<UserService> sutProvider, User user)
|
||||||
{
|
{
|
||||||
user.Id = Guid.NewGuid();
|
|
||||||
user.EmailVerified = true;
|
user.EmailVerified = true;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
|
user.UnknownDeviceVerificationEnabled = true;
|
||||||
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
||||||
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
|
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
|
||||||
|
|
||||||
@ -182,8 +183,8 @@ namespace Bit.Core.Test.Services
|
|||||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_GranType_Is_AuthorizationCode(SutProvider<UserService> sutProvider, User user)
|
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_GranType_Is_AuthorizationCode(SutProvider<UserService> sutProvider, User user)
|
||||||
{
|
{
|
||||||
user.Id = Guid.NewGuid();
|
|
||||||
user.EmailVerified = true;
|
user.EmailVerified = true;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
||||||
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
|
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
|
||||||
|
|
||||||
@ -200,8 +201,8 @@ namespace Bit.Core.Test.Services
|
|||||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_Email_Is_Not_Verified(SutProvider<UserService> sutProvider, User user)
|
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_Email_Is_Not_Verified(SutProvider<UserService> sutProvider, User user)
|
||||||
{
|
{
|
||||||
user.Id = Guid.NewGuid();
|
|
||||||
user.EmailVerified = false;
|
user.EmailVerified = false;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
||||||
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
|
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
|
||||||
|
|
||||||
@ -218,8 +219,8 @@ namespace Bit.Core.Test.Services
|
|||||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_Is_The_First_Device(SutProvider<UserService> sutProvider, User user)
|
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_Is_The_First_Device(SutProvider<UserService> sutProvider, User user)
|
||||||
{
|
{
|
||||||
user.Id = Guid.NewGuid();
|
|
||||||
user.EmailVerified = true;
|
user.EmailVerified = true;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
||||||
|
|
||||||
sutProvider.GetDependency<IDeviceRepository>()
|
sutProvider.GetDependency<IDeviceRepository>()
|
||||||
@ -232,8 +233,8 @@ namespace Bit.Core.Test.Services
|
|||||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_DeviceId_Is_Already_In_Repo(SutProvider<UserService> sutProvider, User user)
|
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_DeviceId_Is_Already_In_Repo(SutProvider<UserService> sutProvider, User user)
|
||||||
{
|
{
|
||||||
user.Id = Guid.NewGuid();
|
|
||||||
user.EmailVerified = true;
|
user.EmailVerified = true;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
||||||
|
|
||||||
sutProvider.GetDependency<IDeviceRepository>()
|
sutProvider.GetDependency<IDeviceRepository>()
|
||||||
@ -249,8 +250,8 @@ namespace Bit.Core.Test.Services
|
|||||||
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_GlobalSettings_2FA_EmailOnNewDeviceLogin_Is_Disabled(SutProvider<UserService> sutProvider, User user)
|
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_GlobalSettings_2FA_EmailOnNewDeviceLogin_Is_Disabled(SutProvider<UserService> sutProvider, User user)
|
||||||
{
|
{
|
||||||
user.Id = Guid.NewGuid();
|
|
||||||
user.EmailVerified = true;
|
user.EmailVerified = true;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
||||||
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
|
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
|
||||||
|
|
||||||
@ -265,5 +266,89 @@ namespace Bit.Core.Test.Services
|
|||||||
|
|
||||||
Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password"));
|
Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
|
public async Task Needs2FABecauseNewDeviceAsync_ReturnsFalse_When_UnknownDeviceVerification_Is_Disabled(SutProvider<UserService> sutProvider, User user)
|
||||||
|
{
|
||||||
|
user.EmailVerified = true;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
|
user.UnknownDeviceVerificationEnabled = false;
|
||||||
|
const string deviceIdToCheck = "7b01b586-b210-499f-8d52-0c3fdaa646fc";
|
||||||
|
const string deviceIdInRepo = "ea29126c-91b7-4cc4-8ce6-00105b37f64a";
|
||||||
|
|
||||||
|
sutProvider.GetDependency<IDeviceRepository>()
|
||||||
|
.GetManyByUserIdAsync(user.Id)
|
||||||
|
.Returns(Task.FromResult<ICollection<Device>>(new List<Device>
|
||||||
|
{
|
||||||
|
new Device { Identifier = deviceIdInRepo }
|
||||||
|
}));
|
||||||
|
|
||||||
|
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
|
||||||
|
|
||||||
|
Assert.False(await sutProvider.Sut.Needs2FABecauseNewDeviceAsync(user, deviceIdToCheck, "password"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
|
public void CanEditDeviceVerificationSettings_ReturnsTrue(SutProvider<UserService> sutProvider, User user)
|
||||||
|
{
|
||||||
|
user.EmailVerified = true;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
|
||||||
|
|
||||||
|
Assert.True(sutProvider.Sut.CanEditDeviceVerificationSettings(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
|
public void CanEditDeviceVerificationSettings_ReturnsFalse_When_GlobalSettings_2FA_EmailOnNewDeviceLogin_Is_Disabled(SutProvider<UserService> sutProvider, User user)
|
||||||
|
{
|
||||||
|
user.EmailVerified = true;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(false);
|
||||||
|
|
||||||
|
Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
|
public void CanEditDeviceVerificationSettings_ReturnsFalse_When_Email_Is_Not_Verified(SutProvider<UserService> sutProvider, User user)
|
||||||
|
{
|
||||||
|
user.EmailVerified = false;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
|
||||||
|
|
||||||
|
Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
|
public void CanEditDeviceVerificationSettings_ReturnsFalse_When_User_Uses_Key_Connector(SutProvider<UserService> sutProvider, User user)
|
||||||
|
{
|
||||||
|
user.EmailVerified = true;
|
||||||
|
user.TwoFactorProviders = null;
|
||||||
|
user.UsesKeyConnector = true;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
|
||||||
|
|
||||||
|
Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory, CustomAutoData(typeof(SutProviderCustomization))]
|
||||||
|
public void CanEditDeviceVerificationSettings_ReturnsFalse_When_User_Has_A_2FA_Already_Set_Up(SutProvider<UserService> sutProvider, User user)
|
||||||
|
{
|
||||||
|
user.EmailVerified = true;
|
||||||
|
user.SetTwoFactorProviders(new Dictionary<TwoFactorProviderType, TwoFactorProvider>
|
||||||
|
{
|
||||||
|
[TwoFactorProviderType.Email] = new TwoFactorProvider
|
||||||
|
{
|
||||||
|
MetaData = new Dictionary<string, object> { ["Email"] = "asdfasf" },
|
||||||
|
Enabled = true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
sutProvider.GetDependency<Settings.IGlobalSettings>().TwoFactorAuth.EmailOnNewDeviceLogin.Returns(true);
|
||||||
|
|
||||||
|
Assert.False(sutProvider.Sut.CanEditDeviceVerificationSettings(user));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
39
test/Core.Test/Utilities/ClaimsExtensionsTests.cs
Normal file
39
test/Core.Test/Utilities/ClaimsExtensionsTests.cs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using Bit.Core.Utilities;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace Bit.Core.Test.Utilities
|
||||||
|
{
|
||||||
|
public class ClaimsExtensionsTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void HasSSOIdP_Returns_True_When_The_Claims_Has_One_Of_Type_IdP_And_Value_Sso()
|
||||||
|
{
|
||||||
|
var claims = new List<Claim> { new Claim("idp", "sso") };
|
||||||
|
Assert.True(claims.HasSsoIdP());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HasSSOIdP_Returns_False_When_The_Claims_Has_One_Of_Type_IdP_And_Value_Is_Not_Sso()
|
||||||
|
{
|
||||||
|
var claims = new List<Claim> { new Claim("idp", "asdfasfd") };
|
||||||
|
Assert.False(claims.HasSsoIdP());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HasSSOIdP_Returns_False_When_The_Claims_Has_No_One_Of_Type_IdP()
|
||||||
|
{
|
||||||
|
var claims = new List<Claim> { new Claim("qweqweq", "sso") };
|
||||||
|
Assert.False(claims.HasSsoIdP());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void HasSSOIdP_Returns_False_When_The_Claims_Are_Empty()
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>();
|
||||||
|
Assert.False(claims.HasSsoIdP());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,245 @@
|
|||||||
|
-- Table: User (UnknownDeviceVerificationEnabled)
|
||||||
|
IF COL_LENGTH('[dbo].[User]', 'UnknownDeviceVerificationEnabled') IS NULL
|
||||||
|
BEGIN
|
||||||
|
ALTER TABLE
|
||||||
|
[dbo].[User]
|
||||||
|
ADD
|
||||||
|
[UnknownDeviceVerificationEnabled] BIT NOT NULL CONSTRAINT D_User_UnknownDeviceVerificationEnabled DEFAULT 1
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- View: User
|
||||||
|
IF EXISTS(SELECT * FROM sys.views WHERE [Name] = 'UserView')
|
||||||
|
BEGIN
|
||||||
|
DROP VIEW [dbo].[UserView]
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE VIEW [dbo].[UserView]
|
||||||
|
AS
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
[dbo].[User]
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- Stored Procedure: User_Create
|
||||||
|
IF OBJECT_ID('[dbo].[User_Create]') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP PROCEDURE [dbo].[User_Create]
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[User_Create]
|
||||||
|
@Id UNIQUEIDENTIFIER OUTPUT,
|
||||||
|
@Name NVARCHAR(50),
|
||||||
|
@Email NVARCHAR(256),
|
||||||
|
@EmailVerified BIT,
|
||||||
|
@MasterPassword NVARCHAR(300),
|
||||||
|
@MasterPasswordHint NVARCHAR(50),
|
||||||
|
@Culture NVARCHAR(10),
|
||||||
|
@SecurityStamp NVARCHAR(50),
|
||||||
|
@TwoFactorProviders NVARCHAR(MAX),
|
||||||
|
@TwoFactorRecoveryCode NVARCHAR(32),
|
||||||
|
@EquivalentDomains NVARCHAR(MAX),
|
||||||
|
@ExcludedGlobalEquivalentDomains NVARCHAR(MAX),
|
||||||
|
@AccountRevisionDate DATETIME2(7),
|
||||||
|
@Key NVARCHAR(MAX),
|
||||||
|
@PublicKey NVARCHAR(MAX),
|
||||||
|
@PrivateKey NVARCHAR(MAX),
|
||||||
|
@Premium BIT,
|
||||||
|
@PremiumExpirationDate DATETIME2(7),
|
||||||
|
@RenewalReminderDate DATETIME2(7),
|
||||||
|
@Storage BIGINT,
|
||||||
|
@MaxStorageGb SMALLINT,
|
||||||
|
@Gateway TINYINT,
|
||||||
|
@GatewayCustomerId VARCHAR(50),
|
||||||
|
@GatewaySubscriptionId VARCHAR(50),
|
||||||
|
@ReferenceData VARCHAR(MAX),
|
||||||
|
@LicenseKey VARCHAR(100),
|
||||||
|
@Kdf TINYINT,
|
||||||
|
@KdfIterations INT,
|
||||||
|
@CreationDate DATETIME2(7),
|
||||||
|
@RevisionDate DATETIME2(7),
|
||||||
|
@ApiKey VARCHAR(30),
|
||||||
|
@ForcePasswordReset BIT = 0,
|
||||||
|
@UsesKeyConnector BIT = 0,
|
||||||
|
@FailedLoginCount INT = 0,
|
||||||
|
@LastFailedLoginDate DATETIME2(7),
|
||||||
|
@UnknownDeviceVerificationEnabled BIT = 1
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
INSERT INTO [dbo].[User]
|
||||||
|
(
|
||||||
|
[Id],
|
||||||
|
[Name],
|
||||||
|
[Email],
|
||||||
|
[EmailVerified],
|
||||||
|
[MasterPassword],
|
||||||
|
[MasterPasswordHint],
|
||||||
|
[Culture],
|
||||||
|
[SecurityStamp],
|
||||||
|
[TwoFactorProviders],
|
||||||
|
[TwoFactorRecoveryCode],
|
||||||
|
[EquivalentDomains],
|
||||||
|
[ExcludedGlobalEquivalentDomains],
|
||||||
|
[AccountRevisionDate],
|
||||||
|
[Key],
|
||||||
|
[PublicKey],
|
||||||
|
[PrivateKey],
|
||||||
|
[Premium],
|
||||||
|
[PremiumExpirationDate],
|
||||||
|
[RenewalReminderDate],
|
||||||
|
[Storage],
|
||||||
|
[MaxStorageGb],
|
||||||
|
[Gateway],
|
||||||
|
[GatewayCustomerId],
|
||||||
|
[GatewaySubscriptionId],
|
||||||
|
[ReferenceData],
|
||||||
|
[LicenseKey],
|
||||||
|
[Kdf],
|
||||||
|
[KdfIterations],
|
||||||
|
[CreationDate],
|
||||||
|
[RevisionDate],
|
||||||
|
[ApiKey],
|
||||||
|
[ForcePasswordReset],
|
||||||
|
[UsesKeyConnector],
|
||||||
|
[FailedLoginCount],
|
||||||
|
[LastFailedLoginDate],
|
||||||
|
[UnknownDeviceVerificationEnabled]
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
@Id,
|
||||||
|
@Name,
|
||||||
|
@Email,
|
||||||
|
@EmailVerified,
|
||||||
|
@MasterPassword,
|
||||||
|
@MasterPasswordHint,
|
||||||
|
@Culture,
|
||||||
|
@SecurityStamp,
|
||||||
|
@TwoFactorProviders,
|
||||||
|
@TwoFactorRecoveryCode,
|
||||||
|
@EquivalentDomains,
|
||||||
|
@ExcludedGlobalEquivalentDomains,
|
||||||
|
@AccountRevisionDate,
|
||||||
|
@Key,
|
||||||
|
@PublicKey,
|
||||||
|
@PrivateKey,
|
||||||
|
@Premium,
|
||||||
|
@PremiumExpirationDate,
|
||||||
|
@RenewalReminderDate,
|
||||||
|
@Storage,
|
||||||
|
@MaxStorageGb,
|
||||||
|
@Gateway,
|
||||||
|
@GatewayCustomerId,
|
||||||
|
@GatewaySubscriptionId,
|
||||||
|
@ReferenceData,
|
||||||
|
@LicenseKey,
|
||||||
|
@Kdf,
|
||||||
|
@KdfIterations,
|
||||||
|
@CreationDate,
|
||||||
|
@RevisionDate,
|
||||||
|
@ApiKey,
|
||||||
|
@ForcePasswordReset,
|
||||||
|
@UsesKeyConnector,
|
||||||
|
@FailedLoginCount,
|
||||||
|
@LastFailedLoginDate,
|
||||||
|
@UnknownDeviceVerificationEnabled
|
||||||
|
)
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
-- Stored Procedure: User_Update
|
||||||
|
IF OBJECT_ID('[dbo].[User_Update]') IS NOT NULL
|
||||||
|
BEGIN
|
||||||
|
DROP PROCEDURE [dbo].[User_Update]
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
||||||
|
CREATE PROCEDURE [dbo].[User_Update]
|
||||||
|
@Id UNIQUEIDENTIFIER,
|
||||||
|
@Name NVARCHAR(50),
|
||||||
|
@Email NVARCHAR(256),
|
||||||
|
@EmailVerified BIT,
|
||||||
|
@MasterPassword NVARCHAR(300),
|
||||||
|
@MasterPasswordHint NVARCHAR(50),
|
||||||
|
@Culture NVARCHAR(10),
|
||||||
|
@SecurityStamp NVARCHAR(50),
|
||||||
|
@TwoFactorProviders NVARCHAR(MAX),
|
||||||
|
@TwoFactorRecoveryCode NVARCHAR(32),
|
||||||
|
@EquivalentDomains NVARCHAR(MAX),
|
||||||
|
@ExcludedGlobalEquivalentDomains NVARCHAR(MAX),
|
||||||
|
@AccountRevisionDate DATETIME2(7),
|
||||||
|
@Key NVARCHAR(MAX),
|
||||||
|
@PublicKey NVARCHAR(MAX),
|
||||||
|
@PrivateKey NVARCHAR(MAX),
|
||||||
|
@Premium BIT,
|
||||||
|
@PremiumExpirationDate DATETIME2(7),
|
||||||
|
@RenewalReminderDate DATETIME2(7),
|
||||||
|
@Storage BIGINT,
|
||||||
|
@MaxStorageGb SMALLINT,
|
||||||
|
@Gateway TINYINT,
|
||||||
|
@GatewayCustomerId VARCHAR(50),
|
||||||
|
@GatewaySubscriptionId VARCHAR(50),
|
||||||
|
@ReferenceData VARCHAR(MAX),
|
||||||
|
@LicenseKey VARCHAR(100),
|
||||||
|
@Kdf TINYINT,
|
||||||
|
@KdfIterations INT,
|
||||||
|
@CreationDate DATETIME2(7),
|
||||||
|
@RevisionDate DATETIME2(7),
|
||||||
|
@ApiKey VARCHAR(30),
|
||||||
|
@ForcePasswordReset BIT = 0,
|
||||||
|
@UsesKeyConnector BIT = 0,
|
||||||
|
@FailedLoginCount INT,
|
||||||
|
@LastFailedLoginDate DATETIME2(7),
|
||||||
|
@UnknownDeviceVerificationEnabled BIT NULL
|
||||||
|
AS
|
||||||
|
BEGIN
|
||||||
|
SET NOCOUNT ON
|
||||||
|
|
||||||
|
UPDATE
|
||||||
|
[dbo].[User]
|
||||||
|
SET
|
||||||
|
[Name] = @Name,
|
||||||
|
[Email] = @Email,
|
||||||
|
[EmailVerified] = @EmailVerified,
|
||||||
|
[MasterPassword] = @MasterPassword,
|
||||||
|
[MasterPasswordHint] = @MasterPasswordHint,
|
||||||
|
[Culture] = @Culture,
|
||||||
|
[SecurityStamp] = @SecurityStamp,
|
||||||
|
[TwoFactorProviders] = @TwoFactorProviders,
|
||||||
|
[TwoFactorRecoveryCode] = @TwoFactorRecoveryCode,
|
||||||
|
[EquivalentDomains] = @EquivalentDomains,
|
||||||
|
[ExcludedGlobalEquivalentDomains] = @ExcludedGlobalEquivalentDomains,
|
||||||
|
[AccountRevisionDate] = @AccountRevisionDate,
|
||||||
|
[Key] = @Key,
|
||||||
|
[PublicKey] = @PublicKey,
|
||||||
|
[PrivateKey] = @PrivateKey,
|
||||||
|
[Premium] = @Premium,
|
||||||
|
[PremiumExpirationDate] = @PremiumExpirationDate,
|
||||||
|
[RenewalReminderDate] = @RenewalReminderDate,
|
||||||
|
[Storage] = @Storage,
|
||||||
|
[MaxStorageGb] = @MaxStorageGb,
|
||||||
|
[Gateway] = @Gateway,
|
||||||
|
[GatewayCustomerId] = @GatewayCustomerId,
|
||||||
|
[GatewaySubscriptionId] = @GatewaySubscriptionId,
|
||||||
|
[ReferenceData] = @ReferenceData,
|
||||||
|
[LicenseKey] = @LicenseKey,
|
||||||
|
[Kdf] = @Kdf,
|
||||||
|
[KdfIterations] = @KdfIterations,
|
||||||
|
[CreationDate] = @CreationDate,
|
||||||
|
[RevisionDate] = @RevisionDate,
|
||||||
|
[ApiKey] = @ApiKey,
|
||||||
|
[ForcePasswordReset] = @ForcePasswordReset,
|
||||||
|
[UsesKeyConnector] = @UsesKeyConnector,
|
||||||
|
[FailedLoginCount] = @FailedLoginCount,
|
||||||
|
[LastFailedLoginDate] = @LastFailedLoginDate,
|
||||||
|
[UnknownDeviceVerificationEnabled] = @UnknownDeviceVerificationEnabled
|
||||||
|
WHERE
|
||||||
|
[Id] = @Id
|
||||||
|
END
|
||||||
|
GO
|
||||||
|
|
1591
util/MySqlMigrations/Migrations/20220524171600_DeviceUnknownVerification.Designer.cs
generated
Normal file
1591
util/MySqlMigrations/Migrations/20220524171600_DeviceUnknownVerification.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Bit.MySqlMigrations.Migrations
|
||||||
|
{
|
||||||
|
public partial class DeviceUnknownVerification : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "UnknownDeviceVerificationEnabled",
|
||||||
|
table: "User",
|
||||||
|
type: "tinyint(1)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "UnknownDeviceVerificationEnabled",
|
||||||
|
table: "User");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1192,6 +1192,9 @@ namespace Bit.MySqlMigrations.Migrations
|
|||||||
.HasMaxLength(32)
|
.HasMaxLength(32)
|
||||||
.HasColumnType("varchar(32)");
|
.HasColumnType("varchar(32)");
|
||||||
|
|
||||||
|
b.Property<bool>("UnknownDeviceVerificationEnabled")
|
||||||
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
b.Property<bool>("UsesKeyConnector")
|
b.Property<bool>("UsesKeyConnector")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
START TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE `User` ADD `UnknownDeviceVerificationEnabled` tinyint(1) NOT NULL DEFAULT 1;
|
||||||
|
|
||||||
|
INSERT INTO `__EFMigrationsHistory` (`MigrationId`, `ProductVersion`)
|
||||||
|
VALUES ('20220524171600_DeviceUnknownVerification', '5.0.12');
|
||||||
|
|
||||||
|
COMMIT;
|
1599
util/PostgresMigrations/Migrations/20220524170740_DeviceUnknownVerification.Designer.cs
generated
Normal file
1599
util/PostgresMigrations/Migrations/20220524170740_DeviceUnknownVerification.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,24 @@
|
|||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
namespace Bit.PostgresMigrations.Migrations
|
||||||
|
{
|
||||||
|
public partial class DeviceUnknownVerification : Migration
|
||||||
|
{
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<bool>(
|
||||||
|
name: "UnknownDeviceVerificationEnabled",
|
||||||
|
table: "User",
|
||||||
|
type: "boolean",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "UnknownDeviceVerificationEnabled",
|
||||||
|
table: "User");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1200,6 +1200,9 @@ namespace Bit.PostgresMigrations.Migrations
|
|||||||
.HasMaxLength(32)
|
.HasMaxLength(32)
|
||||||
.HasColumnType("character varying(32)");
|
.HasColumnType("character varying(32)");
|
||||||
|
|
||||||
|
b.Property<bool>("UnknownDeviceVerificationEnabled")
|
||||||
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
b.Property<bool>("UsesKeyConnector")
|
b.Property<bool>("UsesKeyConnector")
|
||||||
.HasColumnType("boolean");
|
.HasColumnType("boolean");
|
||||||
|
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
START TRANSACTION;
|
||||||
|
|
||||||
|
ALTER TABLE "User" ADD "UnknownDeviceVerificationEnabled" boolean NOT NULL DEFAULT 1;
|
||||||
|
|
||||||
|
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||||
|
VALUES ('20220524170740_DeviceUnknownVerification', '5.0.12');
|
||||||
|
|
||||||
|
COMMIT;
|
Loading…
Reference in New Issue
Block a user