mirror of
https://github.com/bitwarden/server
synced 2026-02-07 20:23:49 +00:00
test(register): [PM-27084] Account Register Uses New Data Types - Added validation tests and ToUser no longer throws bad request.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Exceptions;
|
||||
using Bit.Core.KeyManagement.Models.Api.Request;
|
||||
using Bit.Core.Utilities;
|
||||
|
||||
@@ -68,16 +67,14 @@ public class RegisterFinishRequestModel : IValidatableObject
|
||||
{
|
||||
Email = Email,
|
||||
MasterPasswordHint = MasterPasswordHint,
|
||||
Kdf = MasterPasswordUnlock?.Kdf.KdfType ?? Kdf
|
||||
?? throw new BadRequestException("KdfType couldn't be found on either the MasterPasswordUnlock or the Kdf property passed in."),
|
||||
KdfIterations = MasterPasswordUnlock?.Kdf.Iterations ?? KdfIterations
|
||||
?? throw new BadRequestException("KdfIterations couldn't be found on either the MasterPasswordUnlock or the KdfIterations property passed in."),
|
||||
Kdf = (KdfType)(MasterPasswordUnlock?.Kdf.KdfType ?? Kdf)!,
|
||||
KdfIterations = (int)(MasterPasswordUnlock?.Kdf.Iterations ?? KdfIterations)!,
|
||||
// KdfMemory and KdfParallelism are optional (only used for Argon2id)
|
||||
KdfMemory = MasterPasswordUnlock?.Kdf.Memory ?? KdfMemory,
|
||||
KdfParallelism = MasterPasswordUnlock?.Kdf.Parallelism ?? KdfParallelism,
|
||||
// PM-28827 To be added when MasterPasswordSalt is added to the user column
|
||||
// MasterPasswordSalt = MasterPasswordUnlock?.Salt ?? Email.ToLower().Trim(),
|
||||
Key = MasterPasswordUnlock?.MasterKeyWrappedUserKey ?? UserSymmetricKey ?? throw new BadRequestException("MasterKeyWrappedUserKey couldn't be found on either the MasterPasswordUnlockData or the UserSymmetricKey property passed in."),
|
||||
Key = MasterPasswordUnlock?.MasterKeyWrappedUserKey ?? UserSymmetricKey
|
||||
};
|
||||
|
||||
UserAsymmetricKeys.ToUser(user);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.KeyManagement.Models.Api.Request;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Xunit;
|
||||
|
||||
@@ -7,6 +8,17 @@ namespace Bit.Core.Test.Auth.Models.Api.Request.Accounts;
|
||||
|
||||
public class RegisterFinishRequestModelTests
|
||||
{
|
||||
private static List<System.ComponentModel.DataAnnotations.ValidationResult> Validate(RegisterFinishRequestModel model)
|
||||
{
|
||||
var results = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
|
||||
System.ComponentModel.DataAnnotations.Validator.TryValidateObject(
|
||||
model,
|
||||
new System.ComponentModel.DataAnnotations.ValidationContext(model),
|
||||
results,
|
||||
true);
|
||||
return results;
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public void GetTokenType_Returns_EmailVerification(string email, string masterPasswordHash,
|
||||
@@ -170,4 +182,169 @@ public class RegisterFinishRequestModelTests
|
||||
Assert.Equal(userAsymmetricKeys.PublicKey, result.PublicKey);
|
||||
Assert.Equal(userAsymmetricKeys.EncryptedPrivateKey, result.PrivateKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenBothAuthAndRootHashProvidedButNotEqual_ReturnsMismatchError()
|
||||
{
|
||||
var model = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = "user@example.com",
|
||||
MasterPasswordHash = "root-hash",
|
||||
UserAsymmetricKeys = new KeysRequestModel { PublicKey = "pk", EncryptedPrivateKey = "sk" },
|
||||
// Provide both unlock and authentication with valid KDF so only the mismatch rule fires
|
||||
MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel
|
||||
{
|
||||
Kdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default },
|
||||
MasterKeyWrappedUserKey = "wrapped",
|
||||
Salt = "salt"
|
||||
},
|
||||
MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel
|
||||
{
|
||||
Kdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default },
|
||||
MasterPasswordAuthenticationHash = "auth-hash", // different than root
|
||||
Salt = "salt"
|
||||
},
|
||||
// Provide any valid token so we don't fail token validation
|
||||
EmailVerificationToken = "token"
|
||||
};
|
||||
|
||||
var results = Validate(model);
|
||||
|
||||
Assert.Contains(results, r =>
|
||||
r.ErrorMessage == $"{nameof(MasterPasswordAuthenticationDataRequestModel.MasterPasswordAuthenticationHash)} and root level {nameof(RegisterFinishRequestModel.MasterPasswordHash)} provided and are not equal. Only provide one.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenAuthProvidedButUnlockMissing_ReturnsUnlockMissingError()
|
||||
{
|
||||
var model = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = "user@example.com",
|
||||
UserAsymmetricKeys = new KeysRequestModel { PublicKey = "pk", EncryptedPrivateKey = "sk" },
|
||||
MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel
|
||||
{
|
||||
Kdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default },
|
||||
MasterPasswordAuthenticationHash = "auth-hash",
|
||||
Salt = "salt"
|
||||
},
|
||||
EmailVerificationToken = "token"
|
||||
};
|
||||
|
||||
var results = Validate(model);
|
||||
|
||||
Assert.Contains(results, r => r.ErrorMessage == "MasterPasswordUnlock not found on RequestModel");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenUnlockProvidedButAuthMissing_ReturnsAuthMissingError()
|
||||
{
|
||||
var model = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = "user@example.com",
|
||||
UserAsymmetricKeys = new KeysRequestModel { PublicKey = "pk", EncryptedPrivateKey = "sk" },
|
||||
MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel
|
||||
{
|
||||
Kdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default },
|
||||
MasterKeyWrappedUserKey = "wrapped",
|
||||
Salt = "salt"
|
||||
},
|
||||
EmailVerificationToken = "token"
|
||||
};
|
||||
|
||||
var results = Validate(model);
|
||||
|
||||
Assert.Contains(results, r => r.ErrorMessage == "MasterPasswordAuthentication not found on RequestModel");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenNeitherAuthNorUnlock_AndRootKdfMissing_ReturnsBothRootKdfErrors()
|
||||
{
|
||||
var model = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = "user@example.com",
|
||||
UserAsymmetricKeys = new KeysRequestModel { PublicKey = "pk", EncryptedPrivateKey = "sk" },
|
||||
// No MasterPasswordUnlock, no MasterPasswordAuthentication
|
||||
// No root Kdf and KdfIterations to trigger both errors
|
||||
EmailVerificationToken = "token"
|
||||
};
|
||||
|
||||
var results = Validate(model);
|
||||
|
||||
Assert.Contains(results, r => r.ErrorMessage == $"{nameof(RegisterFinishRequestModel.Kdf)} not found on RequestModel");
|
||||
Assert.Contains(results, r => r.ErrorMessage == $"{nameof(RegisterFinishRequestModel.KdfIterations)} not found on RequestModel");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenNeitherAuthNorUnlock_AndValidRootKdf_IsValid()
|
||||
{
|
||||
var model = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = "user@example.com",
|
||||
UserAsymmetricKeys = new KeysRequestModel { PublicKey = "pk", EncryptedPrivateKey = "sk" },
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
||||
// Memory and Parallelism irrelevant for PBKDF2
|
||||
EmailVerificationToken = "token"
|
||||
};
|
||||
|
||||
var results = Validate(model);
|
||||
|
||||
Assert.DoesNotContain(results, r => r.ErrorMessage?.Contains("Kdf") == true);
|
||||
Assert.Empty(results.Where(r => r.ErrorMessage == "No valid registration token provided"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenAllFieldsValidWithSubModels_IsValid()
|
||||
{
|
||||
var model = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = "user@example.com",
|
||||
UserAsymmetricKeys = new KeysRequestModel { PublicKey = "pk", EncryptedPrivateKey = "sk" },
|
||||
MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel
|
||||
{
|
||||
Kdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default },
|
||||
MasterKeyWrappedUserKey = "wrapped",
|
||||
Salt = "salt"
|
||||
},
|
||||
MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel
|
||||
{
|
||||
Kdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default },
|
||||
MasterPasswordAuthenticationHash = "auth-hash",
|
||||
Salt = "salt"
|
||||
},
|
||||
EmailVerificationToken = "token"
|
||||
};
|
||||
|
||||
var results = Validate(model);
|
||||
|
||||
Assert.Empty(results);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Validate_WhenNoValidRegistrationTokenProvided_ReturnsTokenErrorOnly()
|
||||
{
|
||||
var model = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = "user@example.com",
|
||||
UserAsymmetricKeys = new KeysRequestModel { PublicKey = "pk", EncryptedPrivateKey = "sk" },
|
||||
MasterPasswordUnlock = new MasterPasswordUnlockDataRequestModel
|
||||
{
|
||||
Kdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default },
|
||||
MasterKeyWrappedUserKey = "wrapped",
|
||||
Salt = "salt"
|
||||
},
|
||||
MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel
|
||||
{
|
||||
Kdf = new KdfRequestModel { KdfType = KdfType.PBKDF2_SHA256, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default },
|
||||
MasterPasswordAuthenticationHash = "auth-hash",
|
||||
Salt = "salt"
|
||||
}
|
||||
// No token fields set
|
||||
};
|
||||
|
||||
var results = Validate(model);
|
||||
|
||||
Assert.Single(results);
|
||||
Assert.Equal("No valid registration token provided", results[0].ErrorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -927,119 +927,6 @@ public class AccountsControllerTests : IDisposable
|
||||
emailVerificationToken);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostRegisterFinish_WhenKdfMissingInAllSources_ShouldReturnBadRequest(
|
||||
string email,
|
||||
string emailVerificationToken,
|
||||
string masterPasswordHash,
|
||||
string masterKeyWrappedUserKey,
|
||||
int iterations,
|
||||
string publicKey,
|
||||
string encryptedPrivateKey)
|
||||
{
|
||||
// Arrange: No KDF at root, and no unlock-data present
|
||||
var model = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = email,
|
||||
EmailVerificationToken = emailVerificationToken,
|
||||
MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel
|
||||
{
|
||||
// present but ToUser does not source KDF from here
|
||||
Kdf = new KdfRequestModel { KdfType = KdfType.Argon2id, Iterations = iterations },
|
||||
MasterPasswordAuthenticationHash = masterPasswordHash,
|
||||
Salt = email
|
||||
},
|
||||
MasterPasswordUnlock = null,
|
||||
Kdf = null,
|
||||
KdfIterations = iterations,
|
||||
UserSymmetricKey = masterKeyWrappedUserKey,
|
||||
UserAsymmetricKeys = new KeysRequestModel
|
||||
{
|
||||
PublicKey = publicKey,
|
||||
EncryptedPrivateKey = encryptedPrivateKey
|
||||
}
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostRegisterFinish(model));
|
||||
Assert.Equal("KdfType couldn't be found on either the MasterPasswordUnlock or the Kdf property passed in.", ex.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostRegisterFinish_WhenKdfIterationsMissingInAllSources_ShouldReturnBadRequest(
|
||||
string email,
|
||||
string emailVerificationToken,
|
||||
string masterPasswordHash,
|
||||
string masterKeyWrappedUserKey,
|
||||
KdfType kdfType,
|
||||
string publicKey,
|
||||
string encryptedPrivateKey)
|
||||
{
|
||||
// Arrange: No KdfIterations at root, and no unlock-data present
|
||||
var model = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = email,
|
||||
EmailVerificationToken = emailVerificationToken,
|
||||
MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel
|
||||
{
|
||||
// present but ToUser does not source iterations from here
|
||||
Kdf = new KdfRequestModel { KdfType = kdfType, Iterations = AuthConstants.PBKDF2_ITERATIONS.Default },
|
||||
MasterPasswordAuthenticationHash = masterPasswordHash,
|
||||
Salt = email
|
||||
},
|
||||
MasterPasswordUnlock = null,
|
||||
Kdf = kdfType,
|
||||
KdfIterations = null,
|
||||
UserSymmetricKey = masterKeyWrappedUserKey,
|
||||
UserAsymmetricKeys = new KeysRequestModel
|
||||
{
|
||||
PublicKey = publicKey,
|
||||
EncryptedPrivateKey = encryptedPrivateKey
|
||||
}
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostRegisterFinish(model));
|
||||
Assert.Equal("KdfIterations couldn't be found on either the MasterPasswordUnlock or the KdfIterations property passed in.", ex.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task PostRegisterFinish_WhenKeyMissingInAllSources_ShouldReturnBadRequest(
|
||||
string email,
|
||||
string emailVerificationToken,
|
||||
string masterPasswordHash,
|
||||
int iterations,
|
||||
KdfType kdfType,
|
||||
string publicKey,
|
||||
string encryptedPrivateKey)
|
||||
{
|
||||
// Arrange: No key at root, and no unlock-data present
|
||||
var model = new RegisterFinishRequestModel
|
||||
{
|
||||
Email = email,
|
||||
EmailVerificationToken = emailVerificationToken,
|
||||
MasterPasswordAuthentication = new MasterPasswordAuthenticationDataRequestModel
|
||||
{
|
||||
Kdf = new KdfRequestModel { KdfType = kdfType, Iterations = iterations },
|
||||
MasterPasswordAuthenticationHash = masterPasswordHash,
|
||||
Salt = email
|
||||
},
|
||||
MasterPasswordUnlock = null,
|
||||
Kdf = kdfType,
|
||||
KdfIterations = iterations,
|
||||
UserSymmetricKey = null,
|
||||
UserAsymmetricKeys = new KeysRequestModel
|
||||
{
|
||||
PublicKey = publicKey,
|
||||
EncryptedPrivateKey = encryptedPrivateKey
|
||||
}
|
||||
};
|
||||
|
||||
// Act & Assert
|
||||
var ex = await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostRegisterFinish(model));
|
||||
Assert.Equal("MasterKeyWrappedUserKey couldn't be found on either the MasterPasswordUnlockData or the UserSymmetricKey property passed in.", ex.Message);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public void RegisterFinishRequestModel_Validate_Throws_WhenUnlockAndAuthDataMismatch(
|
||||
string email,
|
||||
|
||||
Reference in New Issue
Block a user