mirror of
https://github.com/bitwarden/server.git
synced 2025-04-05 05:00:19 -05:00
Auth/PM-10130 - Registration with Email Verification - Respect Self-hosted Disable Open Registration flag (#4561)
* PM-10130 - Registration with email verification - respect self hosted disable open registration setting properly in non-org invite scenarios. * PM-10130 - Fix unit tests. * PM-10130 - Update integration tests.
This commit is contained in:
parent
f9017f8e8c
commit
54bd5fa894
@ -37,6 +37,8 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
private readonly IUserService _userService;
|
private readonly IUserService _userService;
|
||||||
private readonly IMailService _mailService;
|
private readonly IMailService _mailService;
|
||||||
|
|
||||||
|
private readonly string _disabledUserRegistrationExceptionMsg = "Open registration has been disabled by the system administrator.";
|
||||||
|
|
||||||
public RegisterUserCommand(
|
public RegisterUserCommand(
|
||||||
IGlobalSettings globalSettings,
|
IGlobalSettings globalSettings,
|
||||||
IOrganizationUserRepository organizationUserRepository,
|
IOrganizationUserRepository organizationUserRepository,
|
||||||
@ -124,8 +126,6 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
|
|
||||||
private void ValidateOrgInviteToken(string orgInviteToken, Guid? orgUserId, User user)
|
private void ValidateOrgInviteToken(string orgInviteToken, Guid? orgUserId, User user)
|
||||||
{
|
{
|
||||||
const string disabledUserRegistrationExceptionMsg = "Open registration has been disabled by the system administrator.";
|
|
||||||
|
|
||||||
var orgInviteTokenProvided = !string.IsNullOrWhiteSpace(orgInviteToken);
|
var orgInviteTokenProvided = !string.IsNullOrWhiteSpace(orgInviteToken);
|
||||||
|
|
||||||
if (orgInviteTokenProvided && orgUserId.HasValue)
|
if (orgInviteTokenProvided && orgUserId.HasValue)
|
||||||
@ -140,7 +140,7 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
|
|
||||||
if (_globalSettings.DisableUserRegistration)
|
if (_globalSettings.DisableUserRegistration)
|
||||||
{
|
{
|
||||||
throw new BadRequestException(disabledUserRegistrationExceptionMsg);
|
throw new BadRequestException(_disabledUserRegistrationExceptionMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BadRequestException("Organization invite token is invalid.");
|
throw new BadRequestException("Organization invite token is invalid.");
|
||||||
@ -152,7 +152,7 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
// as you can't register without them.
|
// as you can't register without them.
|
||||||
if (_globalSettings.DisableUserRegistration)
|
if (_globalSettings.DisableUserRegistration)
|
||||||
{
|
{
|
||||||
throw new BadRequestException(disabledUserRegistrationExceptionMsg);
|
throw new BadRequestException(_disabledUserRegistrationExceptionMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open registration is allowed
|
// Open registration is allowed
|
||||||
@ -233,6 +233,14 @@ public class RegisterUserCommand : IRegisterUserCommand
|
|||||||
public async Task<IdentityResult> RegisterUserViaEmailVerificationToken(User user, string masterPasswordHash,
|
public async Task<IdentityResult> RegisterUserViaEmailVerificationToken(User user, string masterPasswordHash,
|
||||||
string emailVerificationToken)
|
string emailVerificationToken)
|
||||||
{
|
{
|
||||||
|
// We validate open registration on send of initial email and here b/c a user could technically start the
|
||||||
|
// account creation process while open registration is enabled and then finish it after it has been
|
||||||
|
// disabled by the self hosted admin.
|
||||||
|
if (_globalSettings.DisableUserRegistration)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(_disabledUserRegistrationExceptionMsg);
|
||||||
|
}
|
||||||
|
|
||||||
var tokenable = ValidateRegistrationEmailVerificationTokenable(emailVerificationToken, user.Email);
|
var tokenable = ValidateRegistrationEmailVerificationTokenable(emailVerificationToken, user.Email);
|
||||||
|
|
||||||
user.EmailVerified = true;
|
user.EmailVerified = true;
|
||||||
|
@ -39,6 +39,11 @@ public class SendVerificationEmailForRegistrationCommand : ISendVerificationEmai
|
|||||||
|
|
||||||
public async Task<string?> Run(string email, string? name, bool receiveMarketingEmails)
|
public async Task<string?> Run(string email, string? name, bool receiveMarketingEmails)
|
||||||
{
|
{
|
||||||
|
if (_globalSettings.DisableUserRegistration)
|
||||||
|
{
|
||||||
|
throw new BadRequestException("Open registration has been disabled by the system administrator.");
|
||||||
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(email))
|
if (string.IsNullOrWhiteSpace(email))
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(email));
|
throw new ArgumentNullException(nameof(email));
|
||||||
|
@ -367,4 +367,18 @@ public class RegisterUserCommandTests
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task RegisterUserViaEmailVerificationToken_DisabledOpenRegistration_ThrowsBadRequestException(SutProvider<RegisterUserCommand> sutProvider, User user, string masterPasswordHash, string emailVerificationToken)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
sutProvider.GetDependency<IGlobalSettings>()
|
||||||
|
.DisableUserRegistration = true;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
var result = await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.RegisterUserViaEmailVerificationToken(user, masterPasswordHash, emailVerificationToken));
|
||||||
|
Assert.Equal("Open registration has been disabled by the system administrator.", result.Message);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,9 @@ public class SendVerificationEmailForRegistrationCommandTests
|
|||||||
sutProvider.GetDependency<GlobalSettings>()
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
.EnableEmailVerification = true;
|
.EnableEmailVerification = true;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
|
.DisableUserRegistration = false;
|
||||||
|
|
||||||
sutProvider.GetDependency<IMailService>()
|
sutProvider.GetDependency<IMailService>()
|
||||||
.SendRegistrationVerificationEmailAsync(email, Arg.Any<string>())
|
.SendRegistrationVerificationEmailAsync(email, Arg.Any<string>())
|
||||||
.Returns(Task.CompletedTask);
|
.Returns(Task.CompletedTask);
|
||||||
@ -63,6 +66,9 @@ public class SendVerificationEmailForRegistrationCommandTests
|
|||||||
sutProvider.GetDependency<GlobalSettings>()
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
.EnableEmailVerification = true;
|
.EnableEmailVerification = true;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
|
.DisableUserRegistration = false;
|
||||||
|
|
||||||
var mockedToken = "token";
|
var mockedToken = "token";
|
||||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||||
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||||
@ -91,6 +97,9 @@ public class SendVerificationEmailForRegistrationCommandTests
|
|||||||
sutProvider.GetDependency<GlobalSettings>()
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
.EnableEmailVerification = false;
|
.EnableEmailVerification = false;
|
||||||
|
|
||||||
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
|
.DisableUserRegistration = false;
|
||||||
|
|
||||||
var mockedToken = "token";
|
var mockedToken = "token";
|
||||||
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
sutProvider.GetDependency<IDataProtectorTokenFactory<RegistrationEmailVerificationTokenable>>()
|
||||||
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
.Protect(Arg.Any<RegistrationEmailVerificationTokenable>())
|
||||||
@ -103,6 +112,19 @@ public class SendVerificationEmailForRegistrationCommandTests
|
|||||||
Assert.Equal(mockedToken, result);
|
Assert.Equal(mockedToken, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[BitAutoData]
|
||||||
|
public async Task SendVerificationEmailForRegistrationCommand_WhenOpenRegistrationDisabled_ThrowsBadRequestException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||||
|
string email, string name, bool receiveMarketingEmails)
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
|
.DisableUserRegistration = true;
|
||||||
|
|
||||||
|
// Act & Assert
|
||||||
|
await Assert.ThrowsAsync<BadRequestException>(() => sutProvider.Sut.Run(email, name, receiveMarketingEmails));
|
||||||
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData]
|
[BitAutoData]
|
||||||
public async Task SendVerificationEmailForRegistrationCommand_WhenIsExistingUserAndEnableEmailVerificationFalse_ThrowsBadRequestException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
public async Task SendVerificationEmailForRegistrationCommand_WhenIsExistingUserAndEnableEmailVerificationFalse_ThrowsBadRequestException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||||
@ -125,6 +147,9 @@ public class SendVerificationEmailForRegistrationCommandTests
|
|||||||
public async Task SendVerificationEmailForRegistrationCommand_WhenNullEmail_ThrowsArgumentNullException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
public async Task SendVerificationEmailForRegistrationCommand_WhenNullEmail_ThrowsArgumentNullException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||||
string name, bool receiveMarketingEmails)
|
string name, bool receiveMarketingEmails)
|
||||||
{
|
{
|
||||||
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
|
.DisableUserRegistration = false;
|
||||||
|
|
||||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sutProvider.Sut.Run(null, name, receiveMarketingEmails));
|
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sutProvider.Sut.Run(null, name, receiveMarketingEmails));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +158,8 @@ public class SendVerificationEmailForRegistrationCommandTests
|
|||||||
public async Task SendVerificationEmailForRegistrationCommand_WhenEmptyEmail_ThrowsArgumentNullException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
public async Task SendVerificationEmailForRegistrationCommand_WhenEmptyEmail_ThrowsArgumentNullException(SutProvider<SendVerificationEmailForRegistrationCommand> sutProvider,
|
||||||
string name, bool receiveMarketingEmails)
|
string name, bool receiveMarketingEmails)
|
||||||
{
|
{
|
||||||
|
sutProvider.GetDependency<GlobalSettings>()
|
||||||
|
.DisableUserRegistration = false;
|
||||||
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sutProvider.Sut.Run("", name, receiveMarketingEmails));
|
await Assert.ThrowsAsync<ArgumentNullException>(async () => await sutProvider.Sut.Run("", name, receiveMarketingEmails));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,6 +62,28 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
|||||||
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
|
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task PostRegisterSendEmailVerification_DisabledOpenRegistration_ThrowsBadRequestException(string name, bool receiveMarketingEmails)
|
||||||
|
{
|
||||||
|
|
||||||
|
// Localize substitutions to this test.
|
||||||
|
var localFactory = new IdentityApplicationFactory();
|
||||||
|
localFactory.UpdateConfiguration("globalSettings:disableUserRegistration", "true");
|
||||||
|
|
||||||
|
var email = $"test+register+{name}@email.com";
|
||||||
|
var model = new RegisterSendVerificationEmailRequestModel
|
||||||
|
{
|
||||||
|
Email = email,
|
||||||
|
Name = name,
|
||||||
|
ReceiveMarketingEmails = receiveMarketingEmails
|
||||||
|
};
|
||||||
|
|
||||||
|
var context = await localFactory.PostRegisterSendEmailVerificationAsync(model);
|
||||||
|
|
||||||
|
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[BitAutoData(true)]
|
[BitAutoData(true)]
|
||||||
[BitAutoData(false)]
|
[BitAutoData(false)]
|
||||||
@ -198,6 +220,38 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
|||||||
Assert.Equal(kdfParallelism, user.KdfParallelism);
|
Assert.Equal(kdfParallelism, user.KdfParallelism);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[Theory, BitAutoData]
|
||||||
|
public async Task RegistrationWithEmailVerification_OpenRegistrationDisabled_ThrowsBadRequestException([Required] string name, string emailVerificationToken,
|
||||||
|
[StringLength(1000), Required] string masterPasswordHash, [StringLength(50)] string masterPasswordHint, [Required] string userSymmetricKey,
|
||||||
|
[Required] KeysRequestModel userAsymmetricKeys, int kdfMemory, int kdfParallelism)
|
||||||
|
{
|
||||||
|
// Localize substitutions to this test.
|
||||||
|
var localFactory = new IdentityApplicationFactory();
|
||||||
|
localFactory.UpdateConfiguration("globalSettings:disableUserRegistration", "true");
|
||||||
|
|
||||||
|
var email = $"test+register+{name}@email.com";
|
||||||
|
|
||||||
|
// Now we call the finish registration endpoint with the email verification token
|
||||||
|
var registerFinishReqModel = new RegisterFinishRequestModel
|
||||||
|
{
|
||||||
|
Email = email,
|
||||||
|
MasterPasswordHash = masterPasswordHash,
|
||||||
|
MasterPasswordHint = masterPasswordHint,
|
||||||
|
EmailVerificationToken = emailVerificationToken,
|
||||||
|
Kdf = KdfType.PBKDF2_SHA256,
|
||||||
|
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
||||||
|
UserSymmetricKey = userSymmetricKey,
|
||||||
|
UserAsymmetricKeys = userAsymmetricKeys,
|
||||||
|
KdfMemory = kdfMemory,
|
||||||
|
KdfParallelism = kdfParallelism
|
||||||
|
};
|
||||||
|
|
||||||
|
var postRegisterFinishHttpContext = await localFactory.PostRegisterFinishAsync(registerFinishReqModel);
|
||||||
|
|
||||||
|
Assert.Equal(StatusCodes.Status400BadRequest, postRegisterFinishHttpContext.Response.StatusCode);
|
||||||
|
}
|
||||||
|
|
||||||
[Theory, BitAutoData]
|
[Theory, BitAutoData]
|
||||||
public async Task RegistrationWithEmailVerification_WithOrgInviteToken_Succeeds(
|
public async Task RegistrationWithEmailVerification_WithOrgInviteToken_Succeeds(
|
||||||
[StringLength(1000)] string masterPasswordHash, [StringLength(50)] string masterPasswordHint, string userSymmetricKey,
|
[StringLength(1000)] string masterPasswordHash, [StringLength(50)] string masterPasswordHint, string userSymmetricKey,
|
||||||
|
@ -144,6 +144,7 @@ public abstract class WebApplicationFactoryBase<T> : WebApplicationFactory<T>
|
|||||||
|
|
||||||
// Email Verification
|
// Email Verification
|
||||||
{ "globalSettings:enableEmailVerification", "true" },
|
{ "globalSettings:enableEmailVerification", "true" },
|
||||||
|
{ "globalSettings:disableUserRegistration", "false" },
|
||||||
{ "globalSettings:launchDarkly:flagValues:email-verification", "true" }
|
{ "globalSettings:launchDarkly:flagValues:email-verification", "true" }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user