mirror of
https://github.com/bitwarden/server.git
synced 2024-12-23 17:07:42 +01:00
[PM-9826] Remove validation from 2fa GET and mask sensitive data (#4526)
* remove validation from 2fa GET and mask sensitive data
* skip verification check on put email
* disable verification on send-email and reenable on put email
* validate authenticator on set instead of get
* Revert "validate authenticator on set instead of get"
This reverts commit 7bf2084531
.
* fix tests
* fix more tests
* Narrow scope of verify bypass
* Defaulted to false on VerifySecretAsync
* fix default param value
---------
Co-authored-by: Ike Kottlowski <ikottlowski@bitwarden.com>
Co-authored-by: Todd Martin <tmartin@bitwarden.com>
This commit is contained in:
parent
4f4750a0a6
commit
091c03a90c
@ -93,7 +93,7 @@ public class TwoFactorController : Controller
|
|||||||
public async Task<TwoFactorAuthenticatorResponseModel> GetAuthenticator(
|
public async Task<TwoFactorAuthenticatorResponseModel> GetAuthenticator(
|
||||||
[FromBody] SecretVerificationRequestModel model)
|
[FromBody] SecretVerificationRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, false);
|
var user = await CheckAsync(model, false, false);
|
||||||
var response = new TwoFactorAuthenticatorResponseModel(user);
|
var response = new TwoFactorAuthenticatorResponseModel(user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@ -121,7 +121,7 @@ public class TwoFactorController : Controller
|
|||||||
[HttpPost("get-yubikey")]
|
[HttpPost("get-yubikey")]
|
||||||
public async Task<TwoFactorYubiKeyResponseModel> GetYubiKey([FromBody] SecretVerificationRequestModel model)
|
public async Task<TwoFactorYubiKeyResponseModel> GetYubiKey([FromBody] SecretVerificationRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, true);
|
var user = await CheckAsync(model, true, false);
|
||||||
var response = new TwoFactorYubiKeyResponseModel(user);
|
var response = new TwoFactorYubiKeyResponseModel(user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@ -147,7 +147,7 @@ public class TwoFactorController : Controller
|
|||||||
[HttpPost("get-duo")]
|
[HttpPost("get-duo")]
|
||||||
public async Task<TwoFactorDuoResponseModel> GetDuo([FromBody] SecretVerificationRequestModel model)
|
public async Task<TwoFactorDuoResponseModel> GetDuo([FromBody] SecretVerificationRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, true);
|
var user = await CheckAsync(model, true, false);
|
||||||
var response = new TwoFactorDuoResponseModel(user);
|
var response = new TwoFactorDuoResponseModel(user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@ -187,7 +187,7 @@ public class TwoFactorController : Controller
|
|||||||
public async Task<TwoFactorDuoResponseModel> GetOrganizationDuo(string id,
|
public async Task<TwoFactorDuoResponseModel> GetOrganizationDuo(string id,
|
||||||
[FromBody] SecretVerificationRequestModel model)
|
[FromBody] SecretVerificationRequestModel model)
|
||||||
{
|
{
|
||||||
await CheckAsync(model, false);
|
await CheckAsync(model, false, false);
|
||||||
|
|
||||||
var orgIdGuid = new Guid(id);
|
var orgIdGuid = new Guid(id);
|
||||||
if (!await _currentContext.ManagePolicies(orgIdGuid))
|
if (!await _currentContext.ManagePolicies(orgIdGuid))
|
||||||
@ -244,7 +244,7 @@ public class TwoFactorController : Controller
|
|||||||
[HttpPost("get-webauthn")]
|
[HttpPost("get-webauthn")]
|
||||||
public async Task<TwoFactorWebAuthnResponseModel> GetWebAuthn([FromBody] SecretVerificationRequestModel model)
|
public async Task<TwoFactorWebAuthnResponseModel> GetWebAuthn([FromBody] SecretVerificationRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, false);
|
var user = await CheckAsync(model, false, false);
|
||||||
var response = new TwoFactorWebAuthnResponseModel(user);
|
var response = new TwoFactorWebAuthnResponseModel(user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@ -253,7 +253,7 @@ public class TwoFactorController : Controller
|
|||||||
[ApiExplorerSettings(IgnoreApi = true)] // Disable Swagger due to CredentialCreateOptions not converting properly
|
[ApiExplorerSettings(IgnoreApi = true)] // Disable Swagger due to CredentialCreateOptions not converting properly
|
||||||
public async Task<CredentialCreateOptions> GetWebAuthnChallenge([FromBody] SecretVerificationRequestModel model)
|
public async Task<CredentialCreateOptions> GetWebAuthnChallenge([FromBody] SecretVerificationRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, false);
|
var user = await CheckAsync(model, false, false);
|
||||||
var reg = await _userService.StartWebAuthnRegistrationAsync(user);
|
var reg = await _userService.StartWebAuthnRegistrationAsync(user);
|
||||||
return reg;
|
return reg;
|
||||||
}
|
}
|
||||||
@ -288,7 +288,7 @@ public class TwoFactorController : Controller
|
|||||||
[HttpPost("get-email")]
|
[HttpPost("get-email")]
|
||||||
public async Task<TwoFactorEmailResponseModel> GetEmail([FromBody] SecretVerificationRequestModel model)
|
public async Task<TwoFactorEmailResponseModel> GetEmail([FromBody] SecretVerificationRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, false);
|
var user = await CheckAsync(model, false, false);
|
||||||
var response = new TwoFactorEmailResponseModel(user);
|
var response = new TwoFactorEmailResponseModel(user);
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@ -296,7 +296,7 @@ public class TwoFactorController : Controller
|
|||||||
[HttpPost("send-email")]
|
[HttpPost("send-email")]
|
||||||
public async Task SendEmail([FromBody] TwoFactorEmailRequestModel model)
|
public async Task SendEmail([FromBody] TwoFactorEmailRequestModel model)
|
||||||
{
|
{
|
||||||
var user = await CheckAsync(model, false);
|
var user = await CheckAsync(model, false, false);
|
||||||
model.ToUser(user);
|
model.ToUser(user);
|
||||||
await _userService.SendTwoFactorEmailAsync(user);
|
await _userService.SendTwoFactorEmailAsync(user);
|
||||||
}
|
}
|
||||||
@ -433,7 +433,7 @@ public class TwoFactorController : Controller
|
|||||||
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
|
return Task.FromResult(new DeviceVerificationResponseModel(false, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<User> CheckAsync(SecretVerificationRequestModel model, bool premium)
|
private async Task<User> CheckAsync(SecretVerificationRequestModel model, bool premium, bool isSetMethod = true)
|
||||||
{
|
{
|
||||||
var user = await _userService.GetUserByPrincipalAsync(User);
|
var user = await _userService.GetUserByPrincipalAsync(User);
|
||||||
if (user == null)
|
if (user == null)
|
||||||
@ -441,7 +441,7 @@ public class TwoFactorController : Controller
|
|||||||
throw new UnauthorizedAccessException();
|
throw new UnauthorizedAccessException();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!await _userService.VerifySecretAsync(user, model.Secret))
|
if (!await _userService.VerifySecretAsync(user, model.Secret, isSetMethod))
|
||||||
{
|
{
|
||||||
await Task.Delay(2000);
|
await Task.Delay(2000);
|
||||||
throw new BadRequestException(string.Empty, "User verification failed.");
|
throw new BadRequestException(string.Empty, "User verification failed.");
|
||||||
|
@ -59,8 +59,8 @@ public class TwoFactorDuoResponseModel : ResponseModel
|
|||||||
// check Skey and IKey first if they exist
|
// check Skey and IKey first if they exist
|
||||||
if (provider.MetaData.TryGetValue("SKey", out var sKey))
|
if (provider.MetaData.TryGetValue("SKey", out var sKey))
|
||||||
{
|
{
|
||||||
ClientSecret = (string)sKey;
|
ClientSecret = MaskKey((string)sKey);
|
||||||
SecretKey = (string)sKey;
|
SecretKey = MaskKey((string)sKey);
|
||||||
}
|
}
|
||||||
if (provider.MetaData.TryGetValue("IKey", out var iKey))
|
if (provider.MetaData.TryGetValue("IKey", out var iKey))
|
||||||
{
|
{
|
||||||
@ -73,8 +73,8 @@ public class TwoFactorDuoResponseModel : ResponseModel
|
|||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace((string)clientSecret))
|
if (!string.IsNullOrWhiteSpace((string)clientSecret))
|
||||||
{
|
{
|
||||||
ClientSecret = (string)clientSecret;
|
ClientSecret = MaskKey((string)clientSecret);
|
||||||
SecretKey = (string)clientSecret;
|
SecretKey = MaskKey((string)clientSecret);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (provider.MetaData.TryGetValue("ClientId", out var clientId))
|
if (provider.MetaData.TryGetValue("ClientId", out var clientId))
|
||||||
@ -114,4 +114,15 @@ public class TwoFactorDuoResponseModel : ResponseModel
|
|||||||
throw new InvalidDataException("Invalid Duo parameters.");
|
throw new InvalidDataException("Invalid Duo parameters.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string MaskKey(string key)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(key) || key.Length <= 6)
|
||||||
|
{
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mask all but the first 6 characters.
|
||||||
|
return string.Concat(key.AsSpan(0, 6), new string('*', key.Length - 6));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -75,7 +75,7 @@ public interface IUserService
|
|||||||
string GetUserName(ClaimsPrincipal principal);
|
string GetUserName(ClaimsPrincipal principal);
|
||||||
Task SendOTPAsync(User user);
|
Task SendOTPAsync(User user);
|
||||||
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, bool isSettingMFA = false);
|
||||||
|
|
||||||
|
|
||||||
void SetTwoFactorProvider(User user, TwoFactorProviderType type, bool setEnabled = true);
|
void SetTwoFactorProvider(User user, TwoFactorProviderType type, bool setEnabled = true);
|
||||||
|
@ -1342,7 +1342,7 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
"otp:" + user.Email, token);
|
"otp:" + user.Email, token);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> VerifySecretAsync(User user, string secret)
|
public async Task<bool> VerifySecretAsync(User user, string secret, bool isSettingMFA = false)
|
||||||
{
|
{
|
||||||
bool isVerified;
|
bool isVerified;
|
||||||
if (user.HasMasterPassword())
|
if (user.HasMasterPassword())
|
||||||
@ -1354,6 +1354,12 @@ public class UserService : UserManager<User>, IUserService, IDisposable
|
|||||||
isVerified = await CheckPasswordAsync(user, secret) ||
|
isVerified = await CheckPasswordAsync(user, secret) ||
|
||||||
await VerifyOTPAsync(user, secret);
|
await VerifyOTPAsync(user, secret);
|
||||||
}
|
}
|
||||||
|
else if (isSettingMFA)
|
||||||
|
{
|
||||||
|
// this is temporary to allow users to view their MFA settings without invalidating email TOTP
|
||||||
|
// Will be removed with PM-9925
|
||||||
|
isVerified = true;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If they don't have a password at all they can only do OTP
|
// If they don't have a password at all they can only do OTP
|
||||||
|
@ -21,9 +21,9 @@ public class OrganizationTwoFactorDuoResponseModelTests
|
|||||||
// Assert if v4 data Ikey and Skey are set to clientId and clientSecret
|
// Assert if v4 data Ikey and Skey are set to clientId and clientSecret
|
||||||
Assert.NotNull(model);
|
Assert.NotNull(model);
|
||||||
Assert.Equal("clientId", model.ClientId);
|
Assert.Equal("clientId", model.ClientId);
|
||||||
Assert.Equal("clientSecret", model.ClientSecret);
|
Assert.Equal("secret************", model.ClientSecret);
|
||||||
Assert.Equal("clientId", model.IntegrationKey);
|
Assert.Equal("clientId", model.IntegrationKey);
|
||||||
Assert.Equal("clientSecret", model.SecretKey);
|
Assert.Equal("secret************", model.SecretKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -57,9 +57,9 @@ public class OrganizationTwoFactorDuoResponseModelTests
|
|||||||
/// Assert Even if both versions are present priority is given to v4 data
|
/// Assert Even if both versions are present priority is given to v4 data
|
||||||
Assert.NotNull(model);
|
Assert.NotNull(model);
|
||||||
Assert.Equal("clientId", model.ClientId);
|
Assert.Equal("clientId", model.ClientId);
|
||||||
Assert.Equal("clientSecret", model.ClientSecret);
|
Assert.Equal("secret************", model.ClientSecret);
|
||||||
Assert.Equal("clientId", model.IntegrationKey);
|
Assert.Equal("clientId", model.IntegrationKey);
|
||||||
Assert.Equal("clientSecret", model.SecretKey);
|
Assert.Equal("secret************", model.SecretKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -92,12 +92,14 @@ public class OrganizationTwoFactorDuoResponseModelTests
|
|||||||
|
|
||||||
private string GetTwoFactorOrganizationDuoProvidersJson()
|
private string GetTwoFactorOrganizationDuoProvidersJson()
|
||||||
{
|
{
|
||||||
return "{\"6\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
return
|
||||||
|
"{\"6\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetTwoFactorOrganizationDuoV4ProvidersJson()
|
private string GetTwoFactorOrganizationDuoV4ProvidersJson()
|
||||||
{
|
{
|
||||||
return "{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
return
|
||||||
|
"{\"6\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetTwoFactorOrganizationDuoV2ProvidersJson()
|
private string GetTwoFactorOrganizationDuoV2ProvidersJson()
|
||||||
|
@ -21,9 +21,9 @@ public class UserTwoFactorDuoResponseModelTests
|
|||||||
// Assert if v4 data Ikey and Skey are set to clientId and clientSecret
|
// Assert if v4 data Ikey and Skey are set to clientId and clientSecret
|
||||||
Assert.NotNull(model);
|
Assert.NotNull(model);
|
||||||
Assert.Equal("clientId", model.ClientId);
|
Assert.Equal("clientId", model.ClientId);
|
||||||
Assert.Equal("clientSecret", model.ClientSecret);
|
Assert.Equal("secret************", model.ClientSecret);
|
||||||
Assert.Equal("clientId", model.IntegrationKey);
|
Assert.Equal("clientId", model.IntegrationKey);
|
||||||
Assert.Equal("clientSecret", model.SecretKey);
|
Assert.Equal("secret************", model.SecretKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -57,9 +57,9 @@ public class UserTwoFactorDuoResponseModelTests
|
|||||||
// Assert Even if both versions are present priority is given to v4 data
|
// Assert Even if both versions are present priority is given to v4 data
|
||||||
Assert.NotNull(model);
|
Assert.NotNull(model);
|
||||||
Assert.Equal("clientId", model.ClientId);
|
Assert.Equal("clientId", model.ClientId);
|
||||||
Assert.Equal("clientSecret", model.ClientSecret);
|
Assert.Equal("secret************", model.ClientSecret);
|
||||||
Assert.Equal("clientId", model.IntegrationKey);
|
Assert.Equal("clientId", model.IntegrationKey);
|
||||||
Assert.Equal("clientSecret", model.SecretKey);
|
Assert.Equal("secret************", model.SecretKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@ -92,12 +92,14 @@ public class UserTwoFactorDuoResponseModelTests
|
|||||||
|
|
||||||
private string GetTwoFactorDuoProvidersJson()
|
private string GetTwoFactorDuoProvidersJson()
|
||||||
{
|
{
|
||||||
return "{\"2\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
return
|
||||||
|
"{\"2\":{\"Enabled\":true,\"MetaData\":{\"SKey\":\"SKey\",\"IKey\":\"IKey\",\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetTwoFactorDuoV4ProvidersJson()
|
private string GetTwoFactorDuoV4ProvidersJson()
|
||||||
{
|
{
|
||||||
return "{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"clientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
return
|
||||||
|
"{\"2\":{\"Enabled\":true,\"MetaData\":{\"ClientSecret\":\"secretClientSecret\",\"ClientId\":\"clientId\",\"Host\":\"example.com\"}}}";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetTwoFactorDuoV2ProvidersJson()
|
private string GetTwoFactorDuoV2ProvidersJson()
|
||||||
|
Loading…
Reference in New Issue
Block a user