mirror of
https://github.com/bitwarden/server.git
synced 2025-05-02 02:02:18 -05:00
PM-6675 - Remove old registration endpoint (#5585)
* feat : remove old registration endpoint * fix: update integration test user registration to match current registration; We need to keep the IRegistrationCommand.RegisterUser method to JIT user. * fix: updating accounts/profile tests to match current implementations
This commit is contained in:
parent
01a08c5814
commit
1399b1417e
@ -8,6 +8,7 @@ public interface IRegisterUserCommand
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new user, sends a welcome email, and raises the signup reference event.
|
||||
/// This method is used for JIT of organization Users.
|
||||
/// </summary>
|
||||
/// <param name="user">The <see cref="User"/> to create</param>
|
||||
/// <returns><see cref="IdentityResult"/></returns>
|
||||
|
@ -8,7 +8,6 @@ using Bit.Core.Auth.Models.Business.Tokenables;
|
||||
using Bit.Core.Auth.Services;
|
||||
using Bit.Core.Auth.UserFeatures.Registration;
|
||||
using Bit.Core.Auth.UserFeatures.WebAuthnLogin;
|
||||
using Bit.Core.Auth.Utilities;
|
||||
using Bit.Core.Context;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
@ -114,16 +113,6 @@ public class AccountsController : Controller
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("register")]
|
||||
[CaptchaProtected]
|
||||
public async Task<RegisterResponseModel> PostRegister([FromBody] RegisterRequestModel model)
|
||||
{
|
||||
var user = model.ToUser();
|
||||
var identityResult = await _registerUserCommand.RegisterUserViaOrganizationInviteToken(user, model.MasterPasswordHash,
|
||||
model.Token, model.OrganizationUserId);
|
||||
return ProcessRegistrationResult(identityResult, user);
|
||||
}
|
||||
|
||||
[HttpPost("register/send-verification-email")]
|
||||
public async Task<IActionResult> PostRegisterSendVerificationEmail([FromBody] RegisterSendVerificationEmailRequestModel model)
|
||||
{
|
||||
@ -175,8 +164,6 @@ public class AccountsController : Controller
|
||||
}
|
||||
|
||||
return Ok();
|
||||
|
||||
|
||||
}
|
||||
|
||||
[HttpPost("register/finish")]
|
||||
@ -185,7 +172,6 @@ public class AccountsController : Controller
|
||||
var user = model.ToUser();
|
||||
|
||||
// Users will either have an emailed token or an email verification token - not both.
|
||||
|
||||
IdentityResult identityResult = null;
|
||||
|
||||
switch (model.GetTokenType())
|
||||
@ -196,33 +182,27 @@ public class AccountsController : Controller
|
||||
model.EmailVerificationToken);
|
||||
|
||||
return ProcessRegistrationResult(identityResult, user);
|
||||
break;
|
||||
case RegisterFinishTokenType.OrganizationInvite:
|
||||
identityResult = await _registerUserCommand.RegisterUserViaOrganizationInviteToken(user, model.MasterPasswordHash,
|
||||
model.OrgInviteToken, model.OrganizationUserId);
|
||||
|
||||
return ProcessRegistrationResult(identityResult, user);
|
||||
break;
|
||||
case RegisterFinishTokenType.OrgSponsoredFreeFamilyPlan:
|
||||
identityResult = await _registerUserCommand.RegisterUserViaOrganizationSponsoredFreeFamilyPlanInviteToken(user, model.MasterPasswordHash, model.OrgSponsoredFreeFamilyPlanToken);
|
||||
|
||||
return ProcessRegistrationResult(identityResult, user);
|
||||
break;
|
||||
case RegisterFinishTokenType.EmergencyAccessInvite:
|
||||
Debug.Assert(model.AcceptEmergencyAccessId.HasValue);
|
||||
identityResult = await _registerUserCommand.RegisterUserViaAcceptEmergencyAccessInviteToken(user, model.MasterPasswordHash,
|
||||
model.AcceptEmergencyAccessInviteToken, model.AcceptEmergencyAccessId.Value);
|
||||
|
||||
return ProcessRegistrationResult(identityResult, user);
|
||||
break;
|
||||
case RegisterFinishTokenType.ProviderInvite:
|
||||
Debug.Assert(model.ProviderUserId.HasValue);
|
||||
identityResult = await _registerUserCommand.RegisterUserViaProviderInviteToken(user, model.MasterPasswordHash,
|
||||
model.ProviderInviteToken, model.ProviderUserId.Value);
|
||||
|
||||
return ProcessRegistrationResult(identityResult, user);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new BadRequestException("Invalid registration finish request");
|
||||
}
|
||||
|
@ -1,13 +1,6 @@
|
||||
using System.Net.Http.Headers;
|
||||
using Bit.Api.IntegrationTest.Factories;
|
||||
using Bit.Api.IntegrationTest.Helpers;
|
||||
using Bit.Api.Models.Response;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Billing.Enums;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Services;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.Api.IntegrationTest.Controllers;
|
||||
@ -19,7 +12,7 @@ public class AccountsControllerTest : IClassFixture<ApiApplicationFactory>
|
||||
public AccountsControllerTest(ApiApplicationFactory factory) => _factory = factory;
|
||||
|
||||
[Fact]
|
||||
public async Task GetPublicKey()
|
||||
public async Task GetAccountsProfile_success()
|
||||
{
|
||||
var tokens = await _factory.LoginWithNewAccount();
|
||||
var client = _factory.CreateClient();
|
||||
@ -33,36 +26,13 @@ public class AccountsControllerTest : IClassFixture<ApiApplicationFactory>
|
||||
var content = await response.Content.ReadFromJsonAsync<ProfileResponseModel>();
|
||||
Assert.NotNull(content);
|
||||
Assert.Equal("integration-test@bitwarden.com", content.Email);
|
||||
Assert.Null(content.Name);
|
||||
Assert.False(content.EmailVerified);
|
||||
Assert.NotNull(content.Name);
|
||||
Assert.True(content.EmailVerified);
|
||||
Assert.False(content.Premium);
|
||||
Assert.False(content.PremiumFromOrganization);
|
||||
Assert.Equal("en-US", content.Culture);
|
||||
Assert.Null(content.Key);
|
||||
Assert.Null(content.PrivateKey);
|
||||
Assert.NotNull(content.Key);
|
||||
Assert.NotNull(content.PrivateKey);
|
||||
Assert.NotNull(content.SecurityStamp);
|
||||
}
|
||||
|
||||
private async Task<string> SetupOrganizationManagedAccount()
|
||||
{
|
||||
_factory.SubstituteService<IFeatureService>(featureService =>
|
||||
featureService.IsEnabled(FeatureFlagKeys.AccountDeprovisioning).Returns(true));
|
||||
|
||||
// Create the owner account
|
||||
var ownerEmail = $"{Guid.NewGuid()}@bitwarden.com";
|
||||
await _factory.LoginWithNewAccount(ownerEmail);
|
||||
|
||||
// Create the organization
|
||||
var (_organization, _) = await OrganizationTestHelpers.SignUpAsync(_factory, plan: PlanType.EnterpriseAnnually2023,
|
||||
ownerEmail: ownerEmail, passwordManagerSeats: 10, paymentMethod: PaymentMethodType.Card);
|
||||
|
||||
// Create a new organization member
|
||||
var (email, orgUser) = await OrganizationTestHelpers.CreateNewUserWithAccountAsync(_factory, _organization.Id,
|
||||
OrganizationUserType.Custom, new Permissions { AccessReports = true, ManageScim = true });
|
||||
|
||||
// Add a verified domain
|
||||
await OrganizationTestHelpers.CreateVerifiedDomainAsync(_factory, _organization.Id, "bitwarden.com");
|
||||
|
||||
return email;
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,6 @@
|
||||
using Bit.Identity.Models.Request.Accounts;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.TestHost;
|
||||
@ -42,13 +44,23 @@ public class ApiApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
/// <summary>
|
||||
/// Helper for registering and logging in to a new account
|
||||
/// </summary>
|
||||
public async Task<(string Token, string RefreshToken)> LoginWithNewAccount(string email = "integration-test@bitwarden.com", string masterPasswordHash = "master_password_hash")
|
||||
public async Task<(string Token, string RefreshToken)> LoginWithNewAccount(
|
||||
string email = "integration-test@bitwarden.com", string masterPasswordHash = "master_password_hash")
|
||||
{
|
||||
await _identityApplicationFactory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = email,
|
||||
MasterPasswordHash = masterPasswordHash,
|
||||
});
|
||||
await _identityApplicationFactory.RegisterNewIdentityFactoryUserAsync(
|
||||
new RegisterFinishRequestModel
|
||||
{
|
||||
Email = email,
|
||||
MasterPasswordHash = masterPasswordHash,
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
||||
UserAsymmetricKeys = new KeysRequestModel()
|
||||
{
|
||||
PublicKey = "public_key",
|
||||
EncryptedPrivateKey = "private_key"
|
||||
},
|
||||
UserSymmetricKey = "sym_key",
|
||||
});
|
||||
|
||||
return await _identityApplicationFactory.TokenFromPasswordAsync(email, masterPasswordHash);
|
||||
}
|
||||
|
@ -0,0 +1,58 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using AutoFixture;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
|
||||
namespace Bit.Core.Test.Auth.AutoFixture;
|
||||
|
||||
internal class RegisterFinishRequestModelCustomization : ICustomization
|
||||
{
|
||||
[StrictEmailAddress, StringLength(256)]
|
||||
public required string Email { get; set; }
|
||||
public required KdfType Kdf { get; set; }
|
||||
public required int KdfIterations { get; set; }
|
||||
public string? EmailVerificationToken { get; set; }
|
||||
public string? OrgInviteToken { get; set; }
|
||||
public string? OrgSponsoredFreeFamilyPlanToken { get; set; }
|
||||
public string? AcceptEmergencyAccessInviteToken { get; set; }
|
||||
public string? ProviderInviteToken { get; set; }
|
||||
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customize<RegisterFinishRequestModel>(composer => composer
|
||||
.With(o => o.Email, Email)
|
||||
.With(o => o.Kdf, Kdf)
|
||||
.With(o => o.KdfIterations, KdfIterations)
|
||||
.With(o => o.EmailVerificationToken, EmailVerificationToken)
|
||||
.With(o => o.OrgInviteToken, OrgInviteToken)
|
||||
.With(o => o.OrgSponsoredFreeFamilyPlanToken, OrgSponsoredFreeFamilyPlanToken)
|
||||
.With(o => o.AcceptEmergencyAccessInviteToken, AcceptEmergencyAccessInviteToken)
|
||||
.With(o => o.ProviderInviteToken, ProviderInviteToken));
|
||||
}
|
||||
}
|
||||
|
||||
public class RegisterFinishRequestModelCustomizeAttribute : BitCustomizeAttribute
|
||||
{
|
||||
public string _email { get; set; } = "{0}@email.com";
|
||||
public KdfType _kdf { get; set; } = KdfType.PBKDF2_SHA256;
|
||||
public int _kdfIterations { get; set; } = AuthConstants.PBKDF2_ITERATIONS.Default;
|
||||
public string? _emailVerificationToken { get; set; }
|
||||
public string? _orgInviteToken { get; set; }
|
||||
public string? _orgSponsoredFreeFamilyPlanToken { get; set; }
|
||||
public string? _acceptEmergencyAccessInviteToken { get; set; }
|
||||
public string? _providerInviteToken { get; set; }
|
||||
|
||||
public override ICustomization GetCustomization() => new RegisterFinishRequestModelCustomization()
|
||||
{
|
||||
Email = _email,
|
||||
Kdf = _kdf,
|
||||
KdfIterations = _kdfIterations,
|
||||
EmailVerificationToken = _emailVerificationToken,
|
||||
OrgInviteToken = _orgInviteToken,
|
||||
OrgSponsoredFreeFamilyPlanToken = _orgSponsoredFreeFamilyPlanToken,
|
||||
AcceptEmergencyAccessInviteToken = _acceptEmergencyAccessInviteToken,
|
||||
ProviderInviteToken = _providerInviteToken
|
||||
};
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
using Bit.Identity.Models.Request.Accounts;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
@ -40,11 +42,20 @@ public class EventsApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
/// </summary>
|
||||
public async Task<(string Token, string RefreshToken)> LoginWithNewAccount(string email = "integration-test@bitwarden.com", string masterPasswordHash = "master_password_hash")
|
||||
{
|
||||
await _identityApplicationFactory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = email,
|
||||
MasterPasswordHash = masterPasswordHash,
|
||||
});
|
||||
await _identityApplicationFactory.RegisterNewIdentityFactoryUserAsync(
|
||||
new RegisterFinishRequestModel
|
||||
{
|
||||
Email = email,
|
||||
MasterPasswordHash = masterPasswordHash,
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
||||
UserAsymmetricKeys = new KeysRequestModel()
|
||||
{
|
||||
PublicKey = "public_key",
|
||||
EncryptedPrivateKey = "private_key"
|
||||
},
|
||||
UserSymmetricKey = "sym_key",
|
||||
});
|
||||
|
||||
return await _identityApplicationFactory.TokenFromPasswordAsync(email, masterPasswordHash);
|
||||
}
|
||||
|
@ -8,10 +8,8 @@ using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Business.Tokenables;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Tokens;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Identity.Models.Request.Accounts;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
@ -31,24 +29,6 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
||||
_factory = factory;
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostRegister_Success()
|
||||
{
|
||||
var context = await _factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = "test+register@email.com",
|
||||
MasterPasswordHash = "master_password_hash"
|
||||
});
|
||||
|
||||
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||
|
||||
var database = _factory.GetDatabaseContext();
|
||||
var user = await database.Users
|
||||
.SingleAsync(u => u.Email == "test+register@email.com");
|
||||
|
||||
Assert.NotNull(user);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData("invalidEmail")]
|
||||
[BitAutoData("")]
|
||||
@ -154,6 +134,7 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
// marketing emails can stay at top level
|
||||
public async Task RegistrationWithEmailVerification_WithEmailVerificationToken_Succeeds([Required] string name, bool receiveMarketingEmails,
|
||||
[StringLength(1000), Required] string masterPasswordHash, [StringLength(50)] string masterPasswordHint, [Required] string userSymmetricKey,
|
||||
[Required] KeysRequestModel userAsymmetricKeys, int kdfMemory, int kdfParallelism)
|
||||
@ -161,16 +142,6 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
||||
// Localize substitutions to this test.
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
|
||||
// First we must substitute the mail service in order to be able to get a valid email verification token
|
||||
// for the complete registration step
|
||||
string capturedEmailVerificationToken = null;
|
||||
localFactory.SubstituteService<IMailService>(mailService =>
|
||||
{
|
||||
mailService.SendRegistrationVerificationEmailAsync(Arg.Any<string>(), Arg.Do<string>(t => capturedEmailVerificationToken = t))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
});
|
||||
|
||||
// we must first call the send verification email endpoint to trigger the first part of the process
|
||||
var email = $"test+register+{name}@email.com";
|
||||
var sendVerificationEmailReqModel = new RegisterSendVerificationEmailRequestModel
|
||||
@ -183,7 +154,7 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
||||
var sendEmailVerificationResponseHttpContext = await localFactory.PostRegisterSendEmailVerificationAsync(sendVerificationEmailReqModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, sendEmailVerificationResponseHttpContext.Response.StatusCode);
|
||||
Assert.NotNull(capturedEmailVerificationToken);
|
||||
Assert.NotNull(localFactory.RegistrationTokens[email]);
|
||||
|
||||
// Now we call the finish registration endpoint with the email verification token
|
||||
var registerFinishReqModel = new RegisterFinishRequestModel
|
||||
@ -191,7 +162,7 @@ public class AccountsControllerTests : IClassFixture<IdentityApplicationFactory>
|
||||
Email = email,
|
||||
MasterPasswordHash = masterPasswordHash,
|
||||
MasterPasswordHint = masterPasswordHint,
|
||||
EmailVerificationToken = capturedEmailVerificationToken,
|
||||
EmailVerificationToken = localFactory.RegistrationTokens[email],
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
||||
UserSymmetricKey = userSymmetricKey,
|
||||
|
@ -1,11 +1,13 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text.Json;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Entities.Provider;
|
||||
using Bit.Core.AdminConsole.Enums.Provider;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
@ -13,7 +15,6 @@ using Bit.Core.Enums;
|
||||
using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Identity.Models.Request.Accounts;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using Duende.IdentityModel;
|
||||
@ -545,16 +546,15 @@ public class IdentityServerSsoTests
|
||||
{
|
||||
var factory = new IdentityApplicationFactory();
|
||||
|
||||
|
||||
var authorizationCode = new AuthorizationCode
|
||||
{
|
||||
ClientId = "web",
|
||||
CreationTime = DateTime.UtcNow,
|
||||
Lifetime = (int)TimeSpan.FromMinutes(5).TotalSeconds,
|
||||
RedirectUri = "https://localhost:8080/sso-connector.html",
|
||||
RequestedScopes = new[] { "api", "offline_access" },
|
||||
RequestedScopes = ["api", "offline_access"],
|
||||
CodeChallenge = challenge.Sha256(),
|
||||
CodeChallengeMethod = "plain", //
|
||||
CodeChallengeMethod = "plain",
|
||||
Subject = null!, // Temporarily set it to null
|
||||
};
|
||||
|
||||
@ -564,16 +564,20 @@ public class IdentityServerSsoTests
|
||||
.Returns(authorizationCode);
|
||||
});
|
||||
|
||||
// This starts the server and finalizes services
|
||||
var registerResponse = await factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = TestEmail,
|
||||
MasterPasswordHash = "master_password_hash",
|
||||
});
|
||||
|
||||
var userRepository = factory.Services.GetRequiredService<IUserRepository>();
|
||||
var user = await userRepository.GetByEmailAsync(TestEmail);
|
||||
Assert.NotNull(user);
|
||||
var user = await factory.RegisterNewIdentityFactoryUserAsync(
|
||||
new RegisterFinishRequestModel
|
||||
{
|
||||
Email = TestEmail,
|
||||
MasterPasswordHash = "masterPasswordHash",
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
||||
UserAsymmetricKeys = new KeysRequestModel()
|
||||
{
|
||||
PublicKey = "public_key",
|
||||
EncryptedPrivateKey = "private_key"
|
||||
},
|
||||
UserSymmetricKey = "sym_key",
|
||||
});
|
||||
|
||||
var organizationRepository = factory.Services.GetRequiredService<IOrganizationRepository>();
|
||||
var organization = await organizationRepository.CreateAsync(new Organization
|
||||
|
@ -3,11 +3,13 @@ using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.AdminConsole.Enums;
|
||||
using Bit.Core.AdminConsole.Repositories;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Platform.Installations;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Test.Auth.AutoFixture;
|
||||
using Bit.Identity.IdentityServer;
|
||||
using Bit.Identity.Models.Request.Accounts;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
@ -17,6 +19,7 @@ using Xunit;
|
||||
|
||||
namespace Bit.Identity.IntegrationTest.Endpoints;
|
||||
|
||||
[SutProviderCustomize]
|
||||
public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
{
|
||||
private const int SecondsInMinute = 60;
|
||||
@ -27,7 +30,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
public IdentityServerTests(IdentityApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
ReinitializeDbForTests();
|
||||
ReinitializeDbForTests(_factory);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@ -48,18 +51,14 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
AssertHelper.AssertEqualJson(endpointRoot, knownConfigurationRoot);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task TokenEndpoint_GrantTypePassword_Success(string deviceId)
|
||||
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
|
||||
public async Task TokenEndpoint_GrantTypePassword_Success(RegisterFinishRequestModel requestModel)
|
||||
{
|
||||
var username = "test+tokenpassword@email.com";
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
await _factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash"
|
||||
});
|
||||
|
||||
var context = await PostLoginAsync(_factory.Server, username, deviceId, context => context.SetAuthEmail(username));
|
||||
var context = await PostLoginAsync(localFactory.Server, user, requestModel.MasterPasswordHash,
|
||||
context => context.SetAuthEmail(user.Email));
|
||||
|
||||
using var body = await AssertDefaultTokenBodyAsync(context);
|
||||
var root = body.RootElement;
|
||||
@ -73,18 +72,16 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
AssertUserDecryptionOptions(root);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task TokenEndpoint_GrantTypePassword_NoAuthEmailHeader_Fails(string deviceId)
|
||||
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
|
||||
public async Task TokenEndpoint_GrantTypePassword_NoAuthEmailHeader_Fails(
|
||||
RegisterFinishRequestModel requestModel)
|
||||
{
|
||||
var username = "test+noauthemailheader@email.com";
|
||||
requestModel.Email = "test+noauthemailheader@email.com";
|
||||
|
||||
await _factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash",
|
||||
});
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
var context = await PostLoginAsync(_factory.Server, username, deviceId, null);
|
||||
var context = await PostLoginAsync(localFactory.Server, user, requestModel.MasterPasswordHash, null);
|
||||
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
|
||||
|
||||
@ -96,18 +93,17 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task TokenEndpoint_GrantTypePassword_InvalidBase64AuthEmailHeader_Fails(string deviceId)
|
||||
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
|
||||
public async Task TokenEndpoint_GrantTypePassword_InvalidBase64AuthEmailHeader_Fails(
|
||||
RegisterFinishRequestModel requestModel)
|
||||
{
|
||||
var username = "test+badauthheader@email.com";
|
||||
requestModel.Email = "test+badauthheader@email.com";
|
||||
|
||||
await _factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash",
|
||||
});
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
var context = await PostLoginAsync(_factory.Server, username, deviceId, context => context.Request.Headers.Append("Auth-Email", "bad_value"));
|
||||
var context = await PostLoginAsync(localFactory.Server, user, requestModel.MasterPasswordHash,
|
||||
context => context.Request.Headers.Append("Auth-Email", "bad_value"));
|
||||
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
|
||||
|
||||
@ -119,18 +115,17 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task TokenEndpoint_GrantTypePassword_WrongAuthEmailHeader_Fails(string deviceId)
|
||||
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
|
||||
public async Task TokenEndpoint_GrantTypePassword_WrongAuthEmailHeader_Fails(
|
||||
RegisterFinishRequestModel requestModel)
|
||||
{
|
||||
var username = "test+badauthheader@email.com";
|
||||
requestModel.Email = "test+badauthheader@email.com";
|
||||
|
||||
await _factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash",
|
||||
});
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
var context = await PostLoginAsync(_factory.Server, username, deviceId, context => context.SetAuthEmail("bad_value"));
|
||||
var context = await PostLoginAsync(localFactory.Server, user, requestModel.MasterPasswordHash,
|
||||
context => context.SetAuthEmail("bad_value"));
|
||||
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
|
||||
|
||||
@ -142,215 +137,198 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
AssertHelper.AssertJsonProperty(root, "error_description", JsonValueKind.String);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Theory, RegisterFinishRequestModelCustomize]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task TokenEndpoint_GrantTypePassword_WithAllUserTypes_WithSsoPolicyDisabled_WithEnforceSsoPolicyForAllUsersTrue_Success(OrganizationUserType organizationUserType, Guid organizationId, string deviceId, int generatedUsername)
|
||||
public async Task TokenEndpoint_GrantTypePassword_WithAllUserTypes_WithSsoPolicyDisabled_WithEnforceSsoPolicyForAllUsersTrue_Success(
|
||||
OrganizationUserType organizationUserType, RegisterFinishRequestModel requestModel, Guid organizationId, int generatedUsername)
|
||||
{
|
||||
var username = $"{generatedUsername}@example.com";
|
||||
requestModel.Email = $"{generatedUsername}@example.com";
|
||||
|
||||
var server = _factory.WithWebHostBuilder(builder =>
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var server = localFactory.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.UseSetting("globalSettings:sso:enforceSsoPolicyForAllUsers", "true");
|
||||
}).Server;
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
await server.PostAsync("/accounts/register", JsonContent.Create(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash"
|
||||
}));
|
||||
await CreateOrganizationWithSsoPolicyAsync(localFactory,
|
||||
organizationId, user.Email, organizationUserType, ssoPolicyEnabled: false);
|
||||
|
||||
await CreateOrganizationWithSsoPolicyAsync(organizationId, username, organizationUserType, ssoPolicyEnabled: false);
|
||||
|
||||
var context = await PostLoginAsync(server, username, deviceId, context => context.SetAuthEmail(username));
|
||||
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash,
|
||||
context => context.SetAuthEmail(user.Email));
|
||||
|
||||
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Theory, RegisterFinishRequestModelCustomize]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task TokenEndpoint_GrantTypePassword_WithAllUserTypes_WithSsoPolicyDisabled_WithEnforceSsoPolicyForAllUsersFalse_Success(OrganizationUserType organizationUserType, Guid organizationId, string deviceId, int generatedUsername)
|
||||
public async Task TokenEndpoint_GrantTypePassword_WithAllUserTypes_WithSsoPolicyDisabled_WithEnforceSsoPolicyForAllUsersFalse_Success(
|
||||
OrganizationUserType organizationUserType, RegisterFinishRequestModel requestModel, Guid organizationId, int generatedUsername)
|
||||
{
|
||||
var username = $"{generatedUsername}@example.com";
|
||||
requestModel.Email = $"{generatedUsername}@example.com";
|
||||
|
||||
var server = _factory.WithWebHostBuilder(builder =>
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var server = localFactory.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.UseSetting("globalSettings:sso:enforceSsoPolicyForAllUsers", "false");
|
||||
|
||||
}).Server;
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
await server.PostAsync("/accounts/register", JsonContent.Create(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash"
|
||||
}));
|
||||
await CreateOrganizationWithSsoPolicyAsync(
|
||||
localFactory, organizationId, user.Email, organizationUserType, ssoPolicyEnabled: false);
|
||||
|
||||
await CreateOrganizationWithSsoPolicyAsync(organizationId, username, organizationUserType, ssoPolicyEnabled: false);
|
||||
|
||||
var context = await PostLoginAsync(server, username, deviceId, context => context.SetAuthEmail(username));
|
||||
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash,
|
||||
context => context.SetAuthEmail(user.Email));
|
||||
|
||||
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Theory, RegisterFinishRequestModelCustomize]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task TokenEndpoint_GrantTypePassword_WithAllUserTypes_WithSsoPolicyEnabled_WithEnforceSsoPolicyForAllUsersTrue_Throw(OrganizationUserType organizationUserType, Guid organizationId, string deviceId, int generatedUsername)
|
||||
public async Task TokenEndpoint_GrantTypePassword_WithAllUserTypes_WithSsoPolicyEnabled_WithEnforceSsoPolicyForAllUsersTrue_Throw(
|
||||
OrganizationUserType organizationUserType, RegisterFinishRequestModel requestModel, Guid organizationId, int generatedUsername)
|
||||
{
|
||||
var username = $"{generatedUsername}@example.com";
|
||||
requestModel.Email = $"{generatedUsername}@example.com";
|
||||
|
||||
var server = _factory.WithWebHostBuilder(builder =>
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var server = localFactory.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.UseSetting("globalSettings:sso:enforceSsoPolicyForAllUsers", "true");
|
||||
}).Server;
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
await server.PostAsync("/accounts/register", JsonContent.Create(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash"
|
||||
}));
|
||||
await CreateOrganizationWithSsoPolicyAsync(localFactory, organizationId, user.Email, organizationUserType, ssoPolicyEnabled: true);
|
||||
|
||||
await CreateOrganizationWithSsoPolicyAsync(organizationId, username, organizationUserType, ssoPolicyEnabled: true);
|
||||
|
||||
var context = await PostLoginAsync(server, username, deviceId, context => context.SetAuthEmail(username));
|
||||
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash,
|
||||
context => context.SetAuthEmail(user.Email));
|
||||
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
|
||||
await AssertRequiredSsoAuthenticationResponseAsync(context);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Theory, RegisterFinishRequestModelCustomize]
|
||||
[BitAutoData(OrganizationUserType.Owner)]
|
||||
[BitAutoData(OrganizationUserType.Admin)]
|
||||
public async Task TokenEndpoint_GrantTypePassword_WithOwnerOrAdmin_WithSsoPolicyEnabled_WithEnforceSsoPolicyForAllUsersFalse_Success(OrganizationUserType organizationUserType, Guid organizationId, string deviceId, int generatedUsername)
|
||||
public async Task TokenEndpoint_GrantTypePassword_WithOwnerOrAdmin_WithSsoPolicyEnabled_WithEnforceSsoPolicyForAllUsersFalse_Success(
|
||||
OrganizationUserType organizationUserType, RegisterFinishRequestModel requestModel, Guid organizationId, int generatedUsername)
|
||||
{
|
||||
var username = $"{generatedUsername}@example.com";
|
||||
requestModel.Email = $"{generatedUsername}@example.com";
|
||||
|
||||
var server = _factory.WithWebHostBuilder(builder =>
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var server = localFactory.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.UseSetting("globalSettings:sso:enforceSsoPolicyForAllUsers", "false");
|
||||
|
||||
}).Server;
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
await server.PostAsync("/accounts/register", JsonContent.Create(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash"
|
||||
}));
|
||||
await CreateOrganizationWithSsoPolicyAsync(localFactory, organizationId, user.Email, organizationUserType, ssoPolicyEnabled: true);
|
||||
|
||||
await CreateOrganizationWithSsoPolicyAsync(organizationId, username, organizationUserType, ssoPolicyEnabled: true);
|
||||
|
||||
var context = await PostLoginAsync(server, username, deviceId, context => context.SetAuthEmail(username));
|
||||
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash,
|
||||
context => context.SetAuthEmail(user.Email));
|
||||
|
||||
Assert.Equal(StatusCodes.Status200OK, context.Response.StatusCode);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Theory, RegisterFinishRequestModelCustomize]
|
||||
[BitAutoData(OrganizationUserType.User)]
|
||||
[BitAutoData(OrganizationUserType.Custom)]
|
||||
public async Task TokenEndpoint_GrantTypePassword_WithNonOwnerOrAdmin_WithSsoPolicyEnabled_WithEnforceSsoPolicyForAllUsersFalse_Throws(OrganizationUserType organizationUserType, Guid organizationId, string deviceId, int generatedUsername)
|
||||
public async Task TokenEndpoint_GrantTypePassword_WithNonOwnerOrAdmin_WithSsoPolicyEnabled_WithEnforceSsoPolicyForAllUsersFalse_Throws(
|
||||
OrganizationUserType organizationUserType, RegisterFinishRequestModel requestModel, Guid organizationId, int generatedUsername)
|
||||
{
|
||||
var username = $"{generatedUsername}@example.com";
|
||||
requestModel.Email = $"{generatedUsername}@example.com";
|
||||
|
||||
var server = _factory.WithWebHostBuilder(builder =>
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var server = localFactory.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.UseSetting("globalSettings:sso:enforceSsoPolicyForAllUsers", "false");
|
||||
|
||||
}).Server;
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
await server.PostAsync("/accounts/register", JsonContent.Create(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash"
|
||||
}));
|
||||
await CreateOrganizationWithSsoPolicyAsync(localFactory, organizationId, user.Email, organizationUserType, ssoPolicyEnabled: true);
|
||||
|
||||
await CreateOrganizationWithSsoPolicyAsync(organizationId, username, organizationUserType, ssoPolicyEnabled: true);
|
||||
|
||||
var context = await PostLoginAsync(server, username, deviceId, context => context.SetAuthEmail(username));
|
||||
var context = await PostLoginAsync(server, user, requestModel.MasterPasswordHash,
|
||||
context => context.SetAuthEmail(user.Email));
|
||||
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
|
||||
await AssertRequiredSsoAuthenticationResponseAsync(context);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task TokenEndpoint_GrantTypeRefreshToken_Success(string deviceId)
|
||||
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
|
||||
public async Task TokenEndpoint_GrantTypeRefreshToken_Success(RegisterFinishRequestModel requestModel)
|
||||
{
|
||||
var username = "test+tokenrefresh@email.com";
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
|
||||
await _factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash",
|
||||
});
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
var (_, refreshToken) = await _factory.TokenFromPasswordAsync(username, "master_password_hash", deviceId);
|
||||
var (_, refreshToken) = await localFactory.TokenFromPasswordAsync(
|
||||
requestModel.Email, requestModel.MasterPasswordHash);
|
||||
|
||||
var context = await _factory.Server.PostAsync("/connect/token", new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "grant_type", "refresh_token" },
|
||||
{ "client_id", "web" },
|
||||
{ "refresh_token", refreshToken },
|
||||
}));
|
||||
var context = await localFactory.Server.PostAsync("/connect/token",
|
||||
new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "grant_type", "refresh_token" },
|
||||
{ "client_id", "web" },
|
||||
{ "refresh_token", refreshToken },
|
||||
}));
|
||||
|
||||
using var body = await AssertDefaultTokenBodyAsync(context);
|
||||
AssertRefreshTokenExists(body.RootElement);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task TokenEndpoint_GrantTypeClientCredentials_Success(string deviceId)
|
||||
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
|
||||
public async Task TokenEndpoint_GrantTypeClientCredentials_Success(RegisterFinishRequestModel model)
|
||||
{
|
||||
var username = "test+tokenclientcredentials@email.com";
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(model);
|
||||
|
||||
await _factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash",
|
||||
});
|
||||
|
||||
var database = _factory.GetDatabaseContext();
|
||||
var user = await database.Users
|
||||
.FirstAsync(u => u.Email == username);
|
||||
|
||||
var context = await _factory.Server.PostAsync("/connect/token", new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "grant_type", "client_credentials" },
|
||||
{ "client_id", $"user.{user.Id}" },
|
||||
{ "client_secret", user.ApiKey },
|
||||
{ "scope", "api" },
|
||||
{ "DeviceIdentifier", deviceId },
|
||||
{ "DeviceType", DeviceTypeAsString(DeviceType.FirefoxBrowser) },
|
||||
{ "DeviceName", "firefox" },
|
||||
}));
|
||||
var context = await localFactory.Server.PostAsync("/connect/token",
|
||||
new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "grant_type", "client_credentials" },
|
||||
{ "client_id", $"user.{user.Id}" },
|
||||
{ "client_secret", user.ApiKey },
|
||||
{ "scope", "api" },
|
||||
{ "DeviceIdentifier", IdentityApplicationFactory.DefaultDeviceIdentifier },
|
||||
{ "DeviceType", DeviceTypeAsString(DeviceType.FirefoxBrowser) },
|
||||
{ "DeviceName", "firefox" },
|
||||
})
|
||||
);
|
||||
|
||||
await AssertDefaultTokenBodyAsync(context, "api");
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task TokenEndpoint_GrantTypeClientCredentials_AsLegacyUser_NotOnWebClient_Fails(string deviceId)
|
||||
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
|
||||
public async Task TokenEndpoint_GrantTypeClientCredentials_AsLegacyUser_NotOnWebClient_Fails(
|
||||
RegisterFinishRequestModel model,
|
||||
string deviceId)
|
||||
{
|
||||
var server = _factory.WithWebHostBuilder(builder =>
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var server = localFactory.WithWebHostBuilder(builder =>
|
||||
{
|
||||
builder.UseSetting("globalSettings:launchDarkly:flagValues:block-legacy-users", "true");
|
||||
}).Server;
|
||||
|
||||
var username = "test+tokenclientcredentials@email.com";
|
||||
model.Email = "test+tokenclientcredentials@email.com";
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(model);
|
||||
|
||||
|
||||
await server.PostAsync("/accounts/register", JsonContent.Create(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash"
|
||||
}));
|
||||
|
||||
|
||||
var database = _factory.GetDatabaseContext();
|
||||
var user = await database.Users
|
||||
.FirstAsync(u => u.Email == username);
|
||||
|
||||
user.PrivateKey = "EncryptedPrivateKey";
|
||||
// Modify user to be legacy user. We have to fetch the user again to put it in the ef-context
|
||||
// so when we modify change tracking will save the changes.
|
||||
var database = localFactory.GetDatabaseContext();
|
||||
user = await database.Users
|
||||
.FirstAsync(u => u.Email == model.Email);
|
||||
user.Key = null;
|
||||
await database.SaveChangesAsync();
|
||||
|
||||
var context = await server.PostAsync("/connect/token", new FormUrlEncodedContent(
|
||||
@ -362,9 +340,9 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
{ "deviceIdentifier", deviceId },
|
||||
{ "deviceName", "chrome" },
|
||||
{ "grant_type", "password" },
|
||||
{ "username", username },
|
||||
{ "password", "master_password_hash" },
|
||||
}), context => context.SetAuthEmail(username));
|
||||
{ "username", model.Email },
|
||||
{ "password", model.MasterPasswordHash },
|
||||
}), context => context.SetAuthEmail(model.Email));
|
||||
|
||||
Assert.Equal(StatusCodes.Status400BadRequest, context.Response.StatusCode);
|
||||
|
||||
@ -535,23 +513,21 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
Assert.Equal("invalid_client", error);
|
||||
}
|
||||
|
||||
[Theory, BitAutoData]
|
||||
public async Task TokenEndpoint_TooQuickInOneSecond_BlockRequest(string deviceId)
|
||||
[Theory, BitAutoData, RegisterFinishRequestModelCustomize]
|
||||
public async Task TokenEndpoint_TooQuickInOneSecond_BlockRequest(
|
||||
RegisterFinishRequestModel requestModel)
|
||||
{
|
||||
const int AmountInOneSecondAllowed = 10;
|
||||
|
||||
// The rule we are testing is 10 requests in 1 second
|
||||
var username = "test+ratelimiting@email.com";
|
||||
requestModel.Email = "test+ratelimiting@email.com";
|
||||
|
||||
await _factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = username,
|
||||
MasterPasswordHash = "master_password_hash",
|
||||
});
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var user = await localFactory.RegisterNewIdentityFactoryUserAsync(requestModel);
|
||||
|
||||
var database = _factory.GetDatabaseContext();
|
||||
var user = await database.Users
|
||||
.FirstAsync(u => u.Email == username);
|
||||
var database = localFactory.GetDatabaseContext();
|
||||
user = await database.Users
|
||||
.FirstAsync(u => u.Email == user.Email);
|
||||
|
||||
var tasks = new Task<HttpContext>[AmountInOneSecondAllowed + 1];
|
||||
|
||||
@ -573,36 +549,40 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
{ "scope", "api offline_access" },
|
||||
{ "client_id", "web" },
|
||||
{ "deviceType", DeviceTypeAsString(DeviceType.FirefoxBrowser) },
|
||||
{ "deviceIdentifier", deviceId },
|
||||
{ "deviceIdentifier", IdentityApplicationFactory.DefaultDeviceIdentifier },
|
||||
{ "deviceName", "firefox" },
|
||||
{ "grant_type", "password" },
|
||||
{ "username", username },
|
||||
{ "username", user.Email},
|
||||
{ "password", "master_password_hash" },
|
||||
}), context => context.SetAuthEmail(username).SetIp("1.1.1.2"));
|
||||
}), context => context.SetAuthEmail(user.Email).SetIp("1.1.1.2"));
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<HttpContext> PostLoginAsync(TestServer server, string username, string deviceId, Action<HttpContext> extraConfiguration)
|
||||
private async Task<HttpContext> PostLoginAsync(
|
||||
TestServer server, User user, string MasterPasswordHash, Action<HttpContext> extraConfiguration)
|
||||
{
|
||||
return await server.PostAsync("/connect/token", new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "scope", "api offline_access" },
|
||||
{ "client_id", "web" },
|
||||
{ "deviceType", DeviceTypeAsString(DeviceType.FirefoxBrowser) },
|
||||
{ "deviceIdentifier", deviceId },
|
||||
{ "deviceIdentifier", IdentityApplicationFactory.DefaultDeviceIdentifier },
|
||||
{ "deviceName", "firefox" },
|
||||
{ "grant_type", "password" },
|
||||
{ "username", username },
|
||||
{ "password", "master_password_hash" },
|
||||
{ "username", user.Email },
|
||||
{ "password", MasterPasswordHash },
|
||||
}), extraConfiguration);
|
||||
}
|
||||
|
||||
private async Task CreateOrganizationWithSsoPolicyAsync(Guid organizationId, string username, OrganizationUserType organizationUserType, bool ssoPolicyEnabled)
|
||||
private async Task CreateOrganizationWithSsoPolicyAsync(
|
||||
IdentityApplicationFactory localFactory,
|
||||
Guid organizationId,
|
||||
string username, OrganizationUserType organizationUserType, bool ssoPolicyEnabled)
|
||||
{
|
||||
var userRepository = _factory.Services.GetService<IUserRepository>();
|
||||
var organizationRepository = _factory.Services.GetService<IOrganizationRepository>();
|
||||
var organizationUserRepository = _factory.Services.GetService<IOrganizationUserRepository>();
|
||||
var policyRepository = _factory.Services.GetService<IPolicyRepository>();
|
||||
var userRepository = localFactory.Services.GetService<IUserRepository>();
|
||||
var organizationRepository = localFactory.Services.GetService<IOrganizationRepository>();
|
||||
var organizationUserRepository = localFactory.Services.GetService<IOrganizationUserRepository>();
|
||||
var policyRepository = localFactory.Services.GetService<IPolicyRepository>();
|
||||
|
||||
var organization = new Organization
|
||||
{
|
||||
@ -617,7 +597,7 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
await organizationRepository.CreateAsync(organization);
|
||||
|
||||
var user = await userRepository.GetByEmailAsync(username);
|
||||
var organizationUser = new Bit.Core.Entities.OrganizationUser
|
||||
var organizationUser = new OrganizationUser
|
||||
{
|
||||
OrganizationId = organization.Id,
|
||||
UserId = user.Id,
|
||||
@ -703,9 +683,9 @@ public class IdentityServerTests : IClassFixture<IdentityApplicationFactory>
|
||||
(prop) => { Assert.Equal("Object", prop.Name); Assert.Equal("userDecryptionOptions", prop.Value.GetString()); });
|
||||
}
|
||||
|
||||
private void ReinitializeDbForTests()
|
||||
private void ReinitializeDbForTests(IdentityApplicationFactory factory)
|
||||
{
|
||||
var databaseContext = _factory.GetDatabaseContext();
|
||||
var databaseContext = factory.GetDatabaseContext();
|
||||
databaseContext.Policies.RemoveRange(databaseContext.Policies);
|
||||
databaseContext.OrganizationUsers.RemoveRange(databaseContext.OrganizationUsers);
|
||||
databaseContext.Organizations.RemoveRange(databaseContext.Organizations);
|
||||
|
@ -1,8 +1,11 @@
|
||||
using System.Security.Claims;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Bit.Core;
|
||||
using Bit.Core.AdminConsole.Entities;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Auth.Models.Data;
|
||||
using Bit.Core.Auth.Repositories;
|
||||
using Bit.Core.Entities;
|
||||
@ -11,7 +14,6 @@ using Bit.Core.Models.Data;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Identity.Models.Request.Accounts;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
@ -19,6 +21,7 @@ using Duende.IdentityModel;
|
||||
using Duende.IdentityServer.Models;
|
||||
using Duende.IdentityServer.Stores;
|
||||
using LinqToDB;
|
||||
using Microsoft.Extensions.Caching.Distributed;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
@ -61,19 +64,14 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
public async Task TokenEndpoint_GrantTypePassword_UserTwoFactorRequired_TwoFactorProvided_Success()
|
||||
{
|
||||
// Arrange
|
||||
// we can't use the class factory here.
|
||||
var factory = new IdentityApplicationFactory();
|
||||
|
||||
string emailToken = null;
|
||||
factory.SubstituteService<IMailService>(mailService =>
|
||||
// return specified email token from cache
|
||||
var emailToken = "12345678";
|
||||
factory.SubstituteService<IDistributedCache>(distCache =>
|
||||
{
|
||||
mailService.SendTwoFactorEmailAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<string>(),
|
||||
Arg.Do<string>(t => emailToken = t),
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<string>())
|
||||
.Returns(Task.CompletedTask);
|
||||
distCache.GetAsync(Arg.Is<string>(s => s.StartsWith("EmailToken_")))
|
||||
.Returns(Task.FromResult(Encoding.UTF8.GetBytes(emailToken)));
|
||||
});
|
||||
|
||||
// Create Test User
|
||||
@ -102,10 +100,11 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
public async Task TokenEndpoint_GrantTypePassword_InvalidTwoFactorToken_Fails()
|
||||
{
|
||||
// Arrange
|
||||
await CreateUserAsync(_factory, _testEmail, _userEmailTwoFactor);
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
await CreateUserAsync(localFactory, _testEmail, _userEmailTwoFactor);
|
||||
|
||||
// Act
|
||||
var context = await _factory.ContextFromPasswordWithTwoFactorAsync(
|
||||
var context = await localFactory.ContextFromPasswordWithTwoFactorAsync(
|
||||
_testEmail, _testPassword, twoFactorProviderType: "Email");
|
||||
|
||||
// Assert
|
||||
@ -124,16 +123,17 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
public async Task TokenEndpoint_GrantTypePassword_OrgDuoTwoFactorRequired_NoTwoFactorProvided_Fails(string deviceId)
|
||||
{
|
||||
// Arrange
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
var challenge = new string('c', 50);
|
||||
var ssoConfigData = new SsoConfigurationData
|
||||
{
|
||||
MemberDecryptionType = MemberDecryptionType.MasterPassword,
|
||||
};
|
||||
await CreateSsoOrganizationAndUserAsync(
|
||||
_factory, ssoConfigData, challenge, _testEmail, orgTwoFactor: _organizationTwoFactor);
|
||||
localFactory, ssoConfigData, challenge, _testEmail, orgTwoFactor: _organizationTwoFactor);
|
||||
|
||||
// Act
|
||||
var context = await _factory.Server.PostAsync("/connect/token", new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
var context = await localFactory.Server.PostAsync("/connect/token", new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "scope", "api offline_access" },
|
||||
{ "client_id", "web" },
|
||||
@ -156,10 +156,11 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
public async Task TokenEndpoint_GrantTypePassword_RememberTwoFactorType_InvalidTwoFactorToken_Fails()
|
||||
{
|
||||
// Arrange
|
||||
await CreateUserAsync(_factory, _testEmail, _userEmailTwoFactor);
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
await CreateUserAsync(localFactory, _testEmail, _userEmailTwoFactor);
|
||||
|
||||
// Act
|
||||
var context = await _factory.ContextFromPasswordWithTwoFactorAsync(
|
||||
var context = await localFactory.ContextFromPasswordWithTwoFactorAsync(
|
||||
_testEmail, _testPassword, twoFactorProviderType: "Remember");
|
||||
|
||||
// Assert
|
||||
@ -210,13 +211,14 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
public async Task TokenEndpoint_GrantTypeClientCredential_IndvTwoFactorRequired_Success(string deviceId)
|
||||
{
|
||||
// Arrange
|
||||
await CreateUserAsync(_factory, _testEmail, _userEmailTwoFactor);
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
await CreateUserAsync(localFactory, _testEmail, _userEmailTwoFactor);
|
||||
|
||||
var database = _factory.GetDatabaseContext();
|
||||
var database = localFactory.GetDatabaseContext();
|
||||
var user = await database.Users.FirstAsync(u => u.Email == _testEmail);
|
||||
|
||||
// Act
|
||||
var context = await _factory.Server.PostAsync("/connect/token", new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
var context = await localFactory.Server.PostAsync("/connect/token", new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "grant_type", "client_credentials" },
|
||||
{ "client_id", $"user.{user.Id}" },
|
||||
@ -275,16 +277,13 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
{
|
||||
// Arrange
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
string emailToken = null;
|
||||
localFactory.SubstituteService<IMailService>(mailService =>
|
||||
|
||||
// return specified email token from cache
|
||||
var emailToken = "12345678";
|
||||
localFactory.SubstituteService<IDistributedCache>(distCache =>
|
||||
{
|
||||
mailService.SendTwoFactorEmailAsync(
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<string>(),
|
||||
Arg.Do<string>(t => emailToken = t),
|
||||
Arg.Any<string>(),
|
||||
Arg.Any<string>())
|
||||
.Returns(Task.CompletedTask);
|
||||
distCache.GetAsync(Arg.Is<string>(s => s.StartsWith("EmailToken_")))
|
||||
.Returns(Task.FromResult(Encoding.UTF8.GetBytes(emailToken)));
|
||||
});
|
||||
|
||||
// Create Test User
|
||||
@ -379,17 +378,24 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
string userTwoFactor = null)
|
||||
{
|
||||
// Create Test User
|
||||
await factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = testEmail,
|
||||
MasterPasswordHash = _testPassword,
|
||||
});
|
||||
|
||||
var userRepository = factory.Services.GetRequiredService<IUserRepository>();
|
||||
var user = await userRepository.GetByEmailAsync(testEmail);
|
||||
var user = await factory.RegisterNewIdentityFactoryUserAsync(
|
||||
new RegisterFinishRequestModel
|
||||
{
|
||||
Email = testEmail,
|
||||
MasterPasswordHash = _testPassword,
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
||||
UserAsymmetricKeys = new KeysRequestModel()
|
||||
{
|
||||
PublicKey = "public_key",
|
||||
EncryptedPrivateKey = "private_key"
|
||||
},
|
||||
UserSymmetricKey = "sym_key",
|
||||
});
|
||||
Assert.NotNull(user);
|
||||
|
||||
var userService = factory.GetService<IUserService>();
|
||||
var userRepository = factory.Services.GetRequiredService<IUserRepository>();
|
||||
if (userTwoFactor != null)
|
||||
{
|
||||
user.TwoFactorProviders = userTwoFactor;
|
||||
@ -426,16 +432,20 @@ public class IdentityServerTwoFactorTests : IClassFixture<IdentityApplicationFac
|
||||
.Returns(authorizationCode);
|
||||
});
|
||||
|
||||
// Create Test User
|
||||
var registerResponse = await factory.RegisterAsync(new RegisterRequestModel
|
||||
{
|
||||
Email = testEmail,
|
||||
MasterPasswordHash = _testPassword,
|
||||
});
|
||||
|
||||
var userRepository = factory.Services.GetRequiredService<IUserRepository>();
|
||||
var user = await userRepository.GetByEmailAsync(testEmail);
|
||||
Assert.NotNull(user);
|
||||
var user = await factory.RegisterNewIdentityFactoryUserAsync(
|
||||
new RegisterFinishRequestModel
|
||||
{
|
||||
Email = testEmail,
|
||||
MasterPasswordHash = _testPassword,
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
||||
UserAsymmetricKeys = new KeysRequestModel()
|
||||
{
|
||||
PublicKey = "public_key",
|
||||
EncryptedPrivateKey = "private_key"
|
||||
},
|
||||
UserSymmetricKey = "sym_key",
|
||||
});
|
||||
|
||||
var userService = factory.GetService<IUserService>();
|
||||
if (userTwoFactor != null)
|
||||
|
@ -24,6 +24,7 @@
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Identity\Identity.csproj" />
|
||||
<ProjectReference Include="..\Common\Common.csproj" />
|
||||
<ProjectReference Include="..\Core.Test\Core.Test.csproj" />
|
||||
<ProjectReference Include="..\IntegrationTestCommon\IntegrationTestCommon.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
using System.Text.Json;
|
||||
using Bit.Core;
|
||||
using Bit.Core.Auth.Entities;
|
||||
using Bit.Core.Auth.Enums;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Repositories;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Identity.Models.Request.Accounts;
|
||||
using Bit.IntegrationTestCommon.Factories;
|
||||
using Bit.Test.Common.AutoFixture.Attributes;
|
||||
using Bit.Test.Common.Helpers;
|
||||
@ -19,28 +19,16 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
private const string DefaultPassword = "master_password_hash";
|
||||
private const string DefaultUsername = "test@email.qa";
|
||||
private const string DefaultDeviceIdentifier = "test_identifier";
|
||||
private readonly IdentityApplicationFactory _factory;
|
||||
private readonly UserManager<User> _userManager;
|
||||
private readonly IAuthRequestRepository _authRequestRepository;
|
||||
private readonly IDeviceService _deviceService;
|
||||
|
||||
public ResourceOwnerPasswordValidatorTests(IdentityApplicationFactory factory)
|
||||
{
|
||||
_factory = factory;
|
||||
|
||||
_userManager = _factory.GetService<UserManager<User>>();
|
||||
_authRequestRepository = _factory.GetService<IAuthRequestRepository>();
|
||||
_deviceService = _factory.GetService<IDeviceService>();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task ValidateAsync_Success()
|
||||
{
|
||||
// Arrange
|
||||
await EnsureUserCreatedAsync();
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
await EnsureUserCreatedAsync(localFactory);
|
||||
|
||||
// Act
|
||||
var context = await _factory.Server.PostAsync("/connect/token",
|
||||
var context = await localFactory.Server.PostAsync("/connect/token",
|
||||
GetFormUrlEncodedContent(),
|
||||
context => context.SetAuthEmail(DefaultUsername));
|
||||
|
||||
@ -56,10 +44,11 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
public async Task ValidateAsync_AuthEmailHeaderInvalid_InvalidGrantResponse()
|
||||
{
|
||||
// Arrange
|
||||
await EnsureUserCreatedAsync();
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
await EnsureUserCreatedAsync(localFactory);
|
||||
|
||||
// Act
|
||||
var context = await _factory.Server.PostAsync(
|
||||
var context = await localFactory.Server.PostAsync(
|
||||
"/connect/token",
|
||||
GetFormUrlEncodedContent()
|
||||
);
|
||||
@ -75,8 +64,10 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
[Theory, BitAutoData]
|
||||
public async Task ValidateAsync_UserNull_Failure(string username)
|
||||
{
|
||||
// Arrange
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
// Act
|
||||
var context = await _factory.Server.PostAsync("/connect/token",
|
||||
var context = await localFactory.Server.PostAsync("/connect/token",
|
||||
GetFormUrlEncodedContent(username: username),
|
||||
context => context.SetAuthEmail(username));
|
||||
|
||||
@ -105,13 +96,16 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
public async Task ValidateAsync_BadPassword_Failure(string badPassword)
|
||||
{
|
||||
// Arrange
|
||||
await EnsureUserCreatedAsync();
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
await EnsureUserCreatedAsync(localFactory);
|
||||
|
||||
var userManager = localFactory.GetService<UserManager<User>>();
|
||||
|
||||
// Verify the User is not null to ensure the failure is due to bad password
|
||||
Assert.NotNull(await _userManager.FindByEmailAsync(DefaultUsername));
|
||||
Assert.NotNull(await userManager.FindByEmailAsync(DefaultUsername));
|
||||
|
||||
// Act
|
||||
var context = await _factory.Server.PostAsync("/connect/token",
|
||||
var context = await localFactory.Server.PostAsync("/connect/token",
|
||||
GetFormUrlEncodedContent(password: badPassword),
|
||||
context => context.SetAuthEmail(DefaultUsername));
|
||||
|
||||
@ -128,9 +122,12 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
public async Task ValidateAsync_ValidateContextAsync_AuthRequest_NotNull_AgeLessThanOneHour_Success()
|
||||
{
|
||||
// Arrange
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
|
||||
// Ensure User
|
||||
await EnsureUserCreatedAsync();
|
||||
var user = await _userManager.FindByEmailAsync(DefaultUsername);
|
||||
await EnsureUserCreatedAsync(localFactory);
|
||||
var userManager = localFactory.GetService<UserManager<User>>();
|
||||
var user = await userManager.FindByEmailAsync(DefaultUsername);
|
||||
Assert.NotNull(user);
|
||||
|
||||
// Connect Request to User and set CreationDate
|
||||
@ -139,13 +136,14 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
AuthRequestType.AuthenticateAndUnlock,
|
||||
DateTime.UtcNow.AddMinutes(-30)
|
||||
);
|
||||
await _authRequestRepository.CreateAsync(authRequest);
|
||||
var authRequestRepository = localFactory.GetService<IAuthRequestRepository>();
|
||||
await authRequestRepository.CreateAsync(authRequest);
|
||||
|
||||
var expectedAuthRequest = await _authRequestRepository.GetManyByUserIdAsync(user.Id);
|
||||
var expectedAuthRequest = await authRequestRepository.GetManyByUserIdAsync(user.Id);
|
||||
Assert.NotEmpty(expectedAuthRequest);
|
||||
|
||||
// Act
|
||||
var context = await _factory.Server.PostAsync("/connect/token",
|
||||
var context = await localFactory.Server.PostAsync("/connect/token",
|
||||
new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "scope", "api offline_access" },
|
||||
@ -171,9 +169,12 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
public async Task ValidateAsync_ValidateContextAsync_AuthRequest_NotNull_AgeGreaterThanOneHour_Failure()
|
||||
{
|
||||
// Arrange
|
||||
var localFactory = new IdentityApplicationFactory();
|
||||
// Ensure User
|
||||
await EnsureUserCreatedAsync(_factory);
|
||||
var user = await _userManager.FindByEmailAsync(DefaultUsername);
|
||||
await EnsureUserCreatedAsync(localFactory);
|
||||
var userManager = localFactory.GetService<UserManager<User>>();
|
||||
|
||||
var user = await userManager.FindByEmailAsync(DefaultUsername);
|
||||
Assert.NotNull(user);
|
||||
|
||||
// Create AuthRequest
|
||||
@ -184,7 +185,7 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
);
|
||||
|
||||
// Act
|
||||
var context = await _factory.Server.PostAsync("/connect/token",
|
||||
var context = await localFactory.Server.PostAsync("/connect/token",
|
||||
new FormUrlEncodedContent(new Dictionary<string, string>
|
||||
{
|
||||
{ "scope", "api offline_access" },
|
||||
@ -214,19 +215,23 @@ public class ResourceOwnerPasswordValidatorTests : IClassFixture<IdentityApplica
|
||||
Assert.Equal("Username or password is incorrect. Try again.", errorMessage);
|
||||
}
|
||||
|
||||
private async Task EnsureUserCreatedAsync(IdentityApplicationFactory factory = null)
|
||||
private async Task EnsureUserCreatedAsync(IdentityApplicationFactory factory)
|
||||
{
|
||||
factory ??= _factory;
|
||||
// No need to create more users than we need
|
||||
if (await _userManager.FindByEmailAsync(DefaultUsername) == null)
|
||||
{
|
||||
// Register user
|
||||
await factory.RegisterAsync(new RegisterRequestModel
|
||||
// Register user
|
||||
await factory.RegisterNewIdentityFactoryUserAsync(
|
||||
new RegisterFinishRequestModel
|
||||
{
|
||||
Email = DefaultUsername,
|
||||
MasterPasswordHash = DefaultPassword
|
||||
MasterPasswordHash = DefaultPassword,
|
||||
Kdf = KdfType.PBKDF2_SHA256,
|
||||
KdfIterations = AuthConstants.PBKDF2_ITERATIONS.Default,
|
||||
UserAsymmetricKeys = new KeysRequestModel()
|
||||
{
|
||||
PublicKey = "public_key",
|
||||
EncryptedPrivateKey = "private_key"
|
||||
},
|
||||
UserSymmetricKey = "sym_key",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private FormUrlEncodedContent GetFormUrlEncodedContent(
|
||||
|
@ -144,50 +144,6 @@ public class AccountsControllerTests : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostRegister_ShouldRegisterUser()
|
||||
{
|
||||
var passwordHash = "abcdef";
|
||||
var token = "123456";
|
||||
var userGuid = new Guid();
|
||||
_registerUserCommand.RegisterUserViaOrganizationInviteToken(Arg.Any<User>(), passwordHash, token, userGuid)
|
||||
.Returns(Task.FromResult(IdentityResult.Success));
|
||||
var request = new RegisterRequestModel
|
||||
{
|
||||
Name = "Example User",
|
||||
Email = "user@example.com",
|
||||
MasterPasswordHash = passwordHash,
|
||||
MasterPasswordHint = "example",
|
||||
Token = token,
|
||||
OrganizationUserId = userGuid
|
||||
};
|
||||
|
||||
await _sut.PostRegister(request);
|
||||
|
||||
await _registerUserCommand.Received(1).RegisterUserViaOrganizationInviteToken(Arg.Any<User>(), passwordHash, token, userGuid);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostRegister_WhenUserServiceFails_ShouldThrowBadRequestException()
|
||||
{
|
||||
var passwordHash = "abcdef";
|
||||
var token = "123456";
|
||||
var userGuid = new Guid();
|
||||
_registerUserCommand.RegisterUserViaOrganizationInviteToken(Arg.Any<User>(), passwordHash, token, userGuid)
|
||||
.Returns(Task.FromResult(IdentityResult.Failed()));
|
||||
var request = new RegisterRequestModel
|
||||
{
|
||||
Name = "Example User",
|
||||
Email = "user@example.com",
|
||||
MasterPasswordHash = passwordHash,
|
||||
MasterPasswordHint = "example",
|
||||
Token = token,
|
||||
OrganizationUserId = userGuid
|
||||
};
|
||||
|
||||
await Assert.ThrowsAsync<BadRequestException>(() => _sut.PostRegister(request));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[BitAutoData]
|
||||
public async Task PostRegisterSendEmailVerification_WhenTokenReturnedFromCommand_Returns200WithToken(string email, string name, bool receiveMarketingEmails)
|
||||
|
@ -1,23 +1,51 @@
|
||||
using System.Net.Http.Json;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using Bit.Core.Auth.Models.Api.Request.Accounts;
|
||||
using Bit.Core.Entities;
|
||||
using Bit.Core.Enums;
|
||||
using Bit.Core.Services;
|
||||
using Bit.Core.Utilities;
|
||||
using Bit.Identity;
|
||||
using Bit.Identity.Models.Request.Accounts;
|
||||
using Bit.Test.Common.Helpers;
|
||||
using HandlebarsDotNet;
|
||||
using LinqToDB;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using NSubstitute;
|
||||
using Xunit;
|
||||
|
||||
namespace Bit.IntegrationTestCommon.Factories;
|
||||
|
||||
public class IdentityApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
{
|
||||
public const string DefaultDeviceIdentifier = "92b9d953-b9b6-4eaf-9d3e-11d57144dfeb";
|
||||
public const string DefaultUserEmail = "DefaultEmail@bitwarden.com";
|
||||
public const string DefaultUserPasswordHash = "default_password_hash";
|
||||
|
||||
public async Task<HttpContext> RegisterAsync(RegisterRequestModel model)
|
||||
/// <summary>
|
||||
/// A dictionary to store registration tokens for email verification. We cannot substitute the IMailService more than once, so
|
||||
/// we capture the email tokens for new user registration in the constructor. The email must be unique otherwise an error will be thrown.
|
||||
/// </summary>
|
||||
public ConcurrentDictionary<string, string> RegistrationTokens { get; private set; } = new ConcurrentDictionary<string, string>();
|
||||
|
||||
protected override void ConfigureWebHost(IWebHostBuilder builder)
|
||||
{
|
||||
return await Server.PostAsync("/accounts/register", JsonContent.Create(model));
|
||||
// This allows us to use the official registration flow
|
||||
SubstituteService<IMailService>(service =>
|
||||
{
|
||||
service.SendRegistrationVerificationEmailAsync(Arg.Any<string>(), Arg.Any<string>())
|
||||
.ReturnsForAnyArgs(Task.CompletedTask)
|
||||
.AndDoes(call =>
|
||||
{
|
||||
if (!RegistrationTokens.TryAdd(call.ArgAt<string>(0), call.ArgAt<string>(1)))
|
||||
{
|
||||
throw new InvalidOperationException("This email was already registered for new user registration.");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
base.ConfigureWebHost(builder);
|
||||
}
|
||||
|
||||
public async Task<HttpContext> PostRegisterSendEmailVerificationAsync(RegisterSendVerificationEmailRequestModel model)
|
||||
@ -155,4 +183,42 @@ public class IdentityApplicationFactory : WebApplicationFactoryBase<Startup>
|
||||
}));
|
||||
return context;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new user to the Identity Application Factory based on the RegisterFinishRequestModel
|
||||
/// </summary>
|
||||
/// <param name="requestModel">RegisterFinishRequestModel needed to seed data to the test user</param>
|
||||
/// <param name="marketingEmails">optional parameter that is tracked during the inital steps of registration.</param>
|
||||
/// <returns>returns the newly created user</returns>
|
||||
public async Task<User> RegisterNewIdentityFactoryUserAsync(
|
||||
RegisterFinishRequestModel requestModel,
|
||||
bool marketingEmails = true)
|
||||
{
|
||||
var sendVerificationEmailReqModel = new RegisterSendVerificationEmailRequestModel
|
||||
{
|
||||
Email = requestModel.Email,
|
||||
Name = "name",
|
||||
ReceiveMarketingEmails = marketingEmails
|
||||
};
|
||||
|
||||
var sendEmailVerificationResponseHttpContext = await PostRegisterSendEmailVerificationAsync(sendVerificationEmailReqModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status204NoContent, sendEmailVerificationResponseHttpContext.Response.StatusCode);
|
||||
Assert.NotNull(RegistrationTokens[requestModel.Email]);
|
||||
|
||||
// Now we call the finish registration endpoint with the email verification token
|
||||
requestModel.EmailVerificationToken = RegistrationTokens[requestModel.Email];
|
||||
|
||||
var postRegisterFinishHttpContext = await PostRegisterFinishAsync(requestModel);
|
||||
|
||||
Assert.Equal(StatusCodes.Status200OK, postRegisterFinishHttpContext.Response.StatusCode);
|
||||
|
||||
var database = GetDatabaseContext();
|
||||
var user = await database.Users
|
||||
.SingleAsync(u => u.Email == requestModel.Email);
|
||||
|
||||
Assert.NotNull(user);
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user