Skip to content

Commit

Permalink
Merge pull request #31 from bcgov/stories/ECER-595
Browse files Browse the repository at this point in the history
ECER-595: create new user profile endpoint and  tests
  • Loading branch information
ytqsl authored Jan 12, 2024
2 parents f60e666 + 7164476 commit 5778a11
Show file tree
Hide file tree
Showing 22 changed files with 720 additions and 200 deletions.
16 changes: 8 additions & 8 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,19 @@
<PackageVersion Include="coverlet.collector" Version="6.0.0" />
<PackageVersion Include="IdentityModel" Version="6.2.0" />
<PackageVersion Include="MartinCostello.Logging.XUnit" Version="0.3.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.SpaProxy" Version="8.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="8.0.1" />
<PackageVersion Include="Microsoft.AspNetCore.SpaProxy" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.0" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.0.3" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="7.0.3" />
<PackageVersion Include="Microsoft.IdentityModel.JsonWebTokens" Version="7.1.2" />
<PackageVersion Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="7.1.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageVersion Include="Microsoft.PowerPlatform.Dataverse.Client" Version="1.1.16" />
<PackageVersion Include="Microsoft.PowerPlatform.Dataverse.Client" Version="1.1.17" />
<PackageVersion Include="Shouldly" Version="4.2.1" />
<PackageVersion Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageVersion Include="WolverineFx" Version="1.13.1" />
<PackageVersion Include="xunit" Version="2.6.4" />
<PackageVersion Include="WolverineFx" Version="1.13.2" />
<PackageVersion Include="xunit" Version="2.6.5" />
<PackageVersion Include="xunit.categories" Version="2.0.8" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.5.6" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,87 +3,97 @@
using ECER.Infrastructure.Common;
using ECER.Utilities.Hosting;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Oakton;
using Wolverine;

var builder = WebApplication.CreateBuilder(args);
namespace ECER.Clients.RegistryPortal.Server;

var assemblies = ReflectionExtensions.DiscoverLocalAessemblies(prefix: "ECER.");
#pragma warning disable RCS1102 // Make class static
#pragma warning disable S1118 // Utility classes should not have public constructors

builder.Host.UseWolverine(opts =>
public class Program
{
foreach (var assembly in assemblies)
private static async Task Main(string[] args)
{
opts.Discovery.IncludeAssembly(assembly);
opts.Discovery.CustomizeHandlerDiscovery(x =>
var builder = WebApplication.CreateBuilder(args);

var assemblies = ReflectionExtensions.DiscoverLocalAessemblies(prefix: "ECER.");

builder.Host.UseWolverine(opts =>
{
x.Includes.WithNameSuffix("Handlers");
foreach (var assembly in assemblies)
{
opts.Discovery.IncludeAssembly(assembly);
opts.Discovery.CustomizeHandlerDiscovery(x =>
{
x.Includes.WithNameSuffix("Handlers");
});
}
});
}
});
builder.Services.AddAutoMapper(cfg =>
{
cfg.ShouldUseConstructor = constructor => constructor.IsPublic;
}, assemblies);
builder.Services.AddAutoMapper(cfg =>
{
cfg.ShouldUseConstructor = constructor => constructor.IsPublic;
}, assemblies);

builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(opts =>
{
opts.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"));
});
builder.Services.AddProblemDetails();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(opts =>
{
opts.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, $"{Assembly.GetExecutingAssembly().GetName().Name}.xml"));
});
builder.Services.AddProblemDetails();

builder.Services.AddCors(options =>
{
options.AddDefaultPolicy(policy =>
{
var allowedOrigins = builder.Configuration.GetValue("cors:allowedOrigins", string.Empty)!.Split(";");
policy
.WithOrigins(allowedOrigins)
.SetIsOriginAllowedToAllowWildcardSubdomains();
});
});

builder.Services.AddAuthentication("bcsc")
.AddJwtBearer("bceid")
.AddJwtBearer("bcsc", opts =>
{
opts.Events = new JwtBearerEvents
builder.Services.AddCors(options =>
{
OnTokenValidated = async ctx =>
options.AddDefaultPolicy(policy =>
{
await Task.CompletedTask;
var allowedOrigins = builder.Configuration.GetValue("cors:allowedOrigins", string.Empty)!.Split(";");
policy
.WithOrigins(allowedOrigins)
.SetIsOriginAllowedToAllowWildcardSubdomains();
});
});

ctx.Principal!.AddIdentity(new ClaimsIdentity(new[] { new Claim("identity_provider", "bcsc") }));
}
};
opts.Validate();
});
builder.Services.AddAuthentication("bcsc")
.AddJwtBearer("bceid")
.AddJwtBearer("bcsc", opts =>
{
opts.Events = new JwtBearerEvents
{
OnTokenValidated = async ctx =>
{
await Task.CompletedTask;

builder.Services.AddAuthorizationBuilder().AddDefaultPolicy("jwt", policy =>
{
policy.AddAuthenticationSchemes("bcsc", "bceid").RequireAuthenticatedUser();
});
ctx.Principal!.AddIdentity(new ClaimsIdentity(new[] { new Claim("identity_provider", "bcsc") }));
}
};
opts.Validate();
});

builder.Services.AddDistributedMemoryCache();
builder.Services.AddAuthorizationBuilder().AddDefaultPolicy("jwt", policy =>
{
policy.AddAuthenticationSchemes("bcsc", "bceid").RequireAuthenticatedUser();
});

HostConfigurer.ConfigureAll(builder.Services, builder.Configuration);
builder.Services.AddDistributedMemoryCache();

var app = builder.Build();
HostConfigurer.ConfigureAll(builder.Services, builder.Configuration);

app.UseDefaultFiles();
app.UseStaticFiles();
app.MapFallbackToFile("index.html");
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();
var app = builder.Build();

if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.MapFallbackToFile("index.html");
app.UseCors();
app.UseAuthentication();
app.UseAuthorization();

EndpointsRegistrar.RegisterAll(app);
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}

return await app.RunOaktonCommands(args);
EndpointsRegistrar.RegisterAll(app);

await app.RunAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public void Register(IEndpointRouteBuilder endpointRouteBuilder)
var login = AuthenticationService.GetUserLogin(ctx.User);
if (login == null) return TypedResults.Forbid();
var result = await bus.InvokeAsync<UserProfileQueryResponse>(new UserProfileQuery(login.Value.identityProvider, login.Value.id), ct);
if (result == null) return TypedResults.NotFound();
if (result.UserProfile == null) return TypedResults.NotFound();
return TypedResults.Ok(new UserInfoResponse(mapper.Map<UserProfile>(result.UserProfile)));
}).WithOpenApi(op =>
{
Expand All @@ -34,7 +34,7 @@ public void Register(IEndpointRouteBuilder endpointRouteBuilder)
await bus.InvokeAsync<string>(new RegisterNewUserCommand(mapper.Map<Managers.Registry.UserProfile>(request.Profile), new Login(login.Value.identityProvider, login.Value.id)));

return TypedResults.Ok();
});
}).RequireAuthorization();
}
}

Expand All @@ -53,15 +53,38 @@ public record UserInfoResponse(UserProfile UserInfo);
/// <param name="Email">Email address</param>
/// <param name="Phone">Phone number</param>
/// <param name="HomeAddress">The home address</param>
/// <param name="MailingAddress">The mailing addess</param>
public record UserProfile(
string FirstName,
string LastName,
string DateOfBirth,
string? Email,
string? Phone,
string? HomeAddress
string? FirstName,
string? LastName,
DateOnly? DateOfBirth,
string Email,
string Phone,
Address? HomeAddress,
Address? MailingAddress
);

/// <summary>
/// Address
/// </summary>
/// <param name="Line1"></param>
/// <param name="Line2"></param>
/// <param name="City"></param>
/// <param name="PostalCode"></param>
/// <param name="Province"></param>
/// <param name="Country"></param>
public record Address(
string Line1,
string? Line2,
string City,
string PostalCode,
string? Province,
string Country
);

/// <summary>
/// New user request
/// </summary>
public record NewUserRequest
{
[Required]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
using AutoMapper;
using ECER.Managers.Registry;

namespace ECER.Clients.RegistryPortal.Server.Users;

internal sealed class UserInfoMapper : Profile
{
public UserInfoMapper()
{
CreateMap<UserProfileQueryResponse, UserInfoResponse>()
.ForCtorParam(nameof(UserInfoResponse.UserInfo), opts => opts.MapFrom(s => s.UserProfile))
;

CreateMap<Managers.Registry.UserProfile, UserProfile>();
CreateMap<Managers.Registry.UserProfile, UserProfile>().ReverseMap();
CreateMap<Address, Managers.Registry.Address>().ReverseMap();
}
}
18 changes: 14 additions & 4 deletions src/ECER.Managers.Registry/UserHandlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static async Task<UserProfileQueryResponse> Handle(UserProfileQuery query

var registrant = results.Items.SingleOrDefault();

return new UserProfileQueryResponse(mapper.Map<UserProfile?>(registrant));
return new UserProfileQueryResponse(mapper.Map<UserProfile?>(registrant?.Profile));
}

public static async Task<string> Handle(RegisterNewUserCommand cmd, IRegistrantRepository registrantRepository, IMapper mapper)
Expand Down Expand Up @@ -58,9 +58,19 @@ public record UserProfile(
string FirstName,
string LastName,
DateOnly DateOfBirth,
string? Email,
string? Phone,
string? HomeAddress
string Email,
string Phone,
Address HomeAddress,
Address? MailingAddress
);

public record Address(
string Line1,
string Line2,
string City,
string PostalCode,
string? Province,
string Country
);

public record Login(string IdentityProvider, string id);
10 changes: 9 additions & 1 deletion src/ECER.Managers.Registry/UserMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,15 @@ public UserMapper()
.ForCtorParam(nameof(NewRegistrantRequest.UserIdentity), opts => opts.MapFrom(s => s.Login))
;

CreateMap<UserProfile, Resources.Accounts.Registrants.UserProfile>();
CreateMap<UserProfile, Resources.Accounts.Registrants.UserProfile>()
.ReverseMap()
.ValidateMemberList(MemberList.Destination)
;

CreateMap<Address, Resources.Accounts.Registrants.Address>()
.ReverseMap()
.ValidateMemberList(MemberList.Destination)
;

CreateMap<Login, UserIdentity>()
.ForMember(d => d.LastLogin, opts => opts.Ignore())
Expand Down
17 changes: 15 additions & 2 deletions src/ECER.Resources.Accounts/Registrants/IRegistrantRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public record Registrant

public record UserIdentity(string IdentityProvider, string Id)
{
public DateTime LastLogin { get; set; }
public DateTime? LastLogin { get; set; }
}

public record UserProfile
Expand All @@ -43,4 +43,17 @@ public record UserProfile

public string? LastName { get; set; }
public DateOnly? DateOfBirth { get; set; }
}
public string? Email { get; set; }
public string? Phone { get; set; }
public Address? HomeAddress { get; set; }
public Address? MailingAddress { get; set; }
}

public record Address(
string Line1,
string? Line2,
string City,
string PostalCode,
string? Province,
string Country
);
25 changes: 17 additions & 8 deletions src/ECER.Resources.Accounts/Registrants/RegistrantRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,14 @@ public async Task<string> RegisterNew(NewRegistrantRequest request)

context.AddObject(contact);

var authentication = mapper.Map<ECER_Authentication>(request.UserIdentity);
var authentication = new ECER_Authentication(Guid.NewGuid())
{
ECER_ExternalId = request.UserIdentity.Id,
ECER_IdentityProvider = request.UserIdentity.IdentityProvider
};

context.AddObject(authentication);
context.AddLink(contact, nameof(Contact.ECER_Authentication_ContactId), authentication);
context.AddLink(contact, ECER_Authentication.Fields.ECER_Contact_ECER_Authentication_455, authentication);

context.SaveChanges();

Expand All @@ -28,13 +32,18 @@ public async Task<RegistrantQueryResults> Query(RegistrantQuery query)
{
await Task.CompletedTask;

var contacts = from contact in context.ContactSet
join authentication in context.ECER_AuthenticationSet on contact.ContactId equals authentication.ECER_ContactId.Id
select new { contact, authentication };
var qry = from contact in context.ContactSet
join authentication in context.ECER_AuthenticationSet on contact.ContactId equals authentication.ECER_CustomerId.Id
select new { contact, authentication };

if (query.WithIdentity != null) contacts = contacts.Where(r => r.authentication.ECER_IdentityProviderName == query.WithIdentity.IdentityProvider && r.authentication.ECER_ExternalId == query.WithIdentity.Id);
if (query.WithId != null) contacts = contacts.Where(r => r.contact.Id == Guid.Parse(query.WithId));
if (query.WithIdentity != null) qry = qry.Where(r => r.authentication.ECER_IdentityProvider == query.WithIdentity.IdentityProvider && r.authentication.ECER_ExternalId == query.WithIdentity.Id);
if (query.WithId != null) qry = qry.Where(r => r.contact.ContactId.Equals(Guid.Parse(query.WithId)));

return new RegistrantQueryResults(Items: mapper.Map<IEnumerable<Registrant>>(contacts.ToList()));
var contacts = qry.Select(r => r.contact).ToList();
foreach (var contact in contacts)
{
context.LoadProperty(contact, nameof(Contact.ECER_Contact_ECER_Authentication_455));
}
return new RegistrantQueryResults(Items: mapper.Map<IEnumerable<Registrant>>(contacts));
}
}
Loading

0 comments on commit 5778a11

Please sign in to comment.