Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using OpenIddict.Abstractions;
Expand Down Expand Up @@ -43,12 +44,14 @@ public sealed class UserRequestHandler :
private readonly IMessagingServiceClient MessagingServiceClient;
private readonly IClientJwtService ClientJwtService;
private readonly SecurityServiceDbContext DbContext;
private readonly IHostEnvironment HostEnvironment;


public UserRequestHandler(UserManager<ApplicationUser> userManager, IPasswordHasher<ApplicationUser> passwordHasher,
IOptions<ServiceOptions> options, IMessagingServiceClient messagingServiceClient,
IClientJwtService clientJwtService,
SecurityServiceDbContext dbContext) {
SecurityServiceDbContext dbContext,
IHostEnvironment hostEnvironment) {



Expand All @@ -58,6 +61,7 @@ public UserRequestHandler(UserManager<ApplicationUser> userManager, IPasswordHas
this.MessagingServiceClient = messagingServiceClient;
this.ClientJwtService = clientJwtService;
this.DbContext = dbContext;
this.HostEnvironment = hostEnvironment;
}

private TokenResponse TokenResponse;
Expand Down Expand Up @@ -180,7 +184,10 @@ public async Task<Result> Handle(SecurityServiceCommands.CreateUserCommand comma
};

bool hasEmailAddress = String.IsNullOrWhiteSpace(command.EmailAddress) == false;
if (hasEmailAddress == false)
bool shouldUseProvidedPassword = hasEmailAddress == false ||
(this.HostEnvironment.IsEnvironment("IntegrationTest") &&
this.Options.Value.UserOptions.UseProvidedPasswordForEmailUsers);
if (shouldUseProvidedPassword)
{
Result<String> passwordValueResult = String.IsNullOrEmpty(command.Password) ? PasswordGenerator.GenerateRandomPassword(this.Options.Value.PasswordOptions) : command.Password;

Expand Down
2 changes: 2 additions & 0 deletions SecurityService.BusinessLogic/SecurityServiceOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ public class UserOptions

public Boolean RequireUniqueEmail { get; set; }

public Boolean UseProvidedPasswordForEmailUsers { get; set; }

#endregion
}

Expand Down
1 change: 1 addition & 0 deletions SecurityService.IntegrationTests/Common/DockerHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ public override ContainerBuilder SetupSecurityServiceContainer()
environmentVariables.Add("ServiceOptions:PasswordOptions:RequireDigit", "false");
environmentVariables.Add("ServiceOptions:PasswordOptions:RequireUpperCase", "false");
environmentVariables.Add("ServiceOptions:UserOptions:RequireUniqueEmail", "false");
environmentVariables.Add("ServiceOptions:UserOptions:UseProvidedPasswordForEmailUsers", "true");
environmentVariables.Add("ServiceOptions:SignInOptions:RequireConfirmedEmail", "false");
environmentVariables.Add("ServiceOptions:ManagementBootstrap:Enabled", "true");
environmentVariables.Add("ServiceOptions:ManagementBootstrap:ClientId", "management-bootstrap");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ public override ContainerBuilder SetupSecurityServiceContainer()
environmentVariables.Add($"ServiceOptions:PasswordOptions:RequireDigit","false");
environmentVariables.Add($"ServiceOptions:PasswordOptions:RequireUpperCase","false");
environmentVariables.Add($"ServiceOptions:UserOptions:RequireUniqueEmail","false");
environmentVariables.Add("ServiceOptions:UserOptions:UseProvidedPasswordForEmailUsers", "true");
environmentVariables.Add($"ServiceOptions:SignInOptions:RequireConfirmedEmail","false");
environmentVariables.Add("ServiceOptions:ManagementBootstrap:Enabled", "true");
environmentVariables.Add("ServiceOptions:ManagementBootstrap:ClientId", "management-bootstrap");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting.Internal;
using Microsoft.IdentityModel.Tokens;
using SecurityService.BusinessLogic;
using SecurityService.BusinessLogic.Requests;
Expand All @@ -22,7 +24,8 @@ public static ServiceProvider Create(
Action<ServiceOptions>? configureOptions = null,
TimeSpan? passwordTokenLifespan = null,
bool useSqlite = false,
SaveChangesInterceptor? saveChangesInterceptor = null)
SaveChangesInterceptor? saveChangesInterceptor = null,
string environmentName = "UnitTest")
{
var rsa = RSA.Create(2048);
var key = new RsaSecurityKey(rsa);
Expand All @@ -39,6 +42,11 @@ public static ServiceProvider Create(
configureOptions?.Invoke(options);
});
services.AddSingleton<IClientJwtService, ClientJwtService>();
services.AddSingleton<IHostEnvironment>(new HostingEnvironment
{
EnvironmentName = environmentName,
ApplicationName = typeof(TestServiceProviderFactory).Assembly.GetName().Name
});
services.AddLogging();
services.AddDataProtection().UseEphemeralDataProtectionProvider();
if (passwordTokenLifespan.HasValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,48 @@ public async Task CreateUser_WithEmailAddress_DoesNotCreateAnInitialPassword()
user.PasswordHash.ShouldBeNull();
}

[Fact]
public async Task CreateUser_WithEmailAddressInIntegrationTestMode_UsesProvidedPasswordWhenEnabled()
{
using var provider = TestServiceProviderFactory.Create(
nameof(this.CreateUser_WithEmailAddressInIntegrationTestMode_UsesProvidedPasswordWhenEnabled),
configureOptions: options => options.UserOptions.UseProvidedPasswordForEmailUsers = true,
environmentName: "IntegrationTest");
var mediator = provider.GetRequiredService<IMediator>();

var result = await mediator.Send(new SecurityServiceCommands.CreateUserCommand(
"Alice", null, "Tester", "alice", "SuppliedPassword1!", "alice@example.com", null,
new Dictionary<string, string>(), new List<string>()));

result.IsSuccess.ShouldBeTrue();
using var scope = provider.CreateScope();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var user = await userManager.FindByNameAsync("alice");
user.ShouldNotBeNull();
(await userManager.CheckPasswordAsync(user, "SuppliedPassword1!")).ShouldBeTrue();
}

[Fact]
public async Task CreateUser_WithEmailAddressOutsideIntegrationTestMode_IgnoresProvidedPassword()
{
using var provider = TestServiceProviderFactory.Create(
nameof(this.CreateUser_WithEmailAddressOutsideIntegrationTestMode_IgnoresProvidedPassword),
configureOptions: options => options.UserOptions.UseProvidedPasswordForEmailUsers = true,
environmentName: "Development");
var mediator = provider.GetRequiredService<IMediator>();

var result = await mediator.Send(new SecurityServiceCommands.CreateUserCommand(
"Alice", null, "Tester", "alice", "SuppliedPassword1!", "alice@example.com", null,
new Dictionary<string, string>(), new List<string>()));

result.IsSuccess.ShouldBeTrue();
using var scope = provider.CreateScope();
var userManager = scope.ServiceProvider.GetRequiredService<UserManager<ApplicationUser>>();
var user = await userManager.FindByNameAsync("alice");
user.ShouldNotBeNull();
user.PasswordHash.ShouldBeNull();
}

[Fact]
public async Task UserLifecycle_CreateGetAndList_Works()
{
Expand Down
Loading