Skip to content

Commit

Permalink
fix(apis-user): Resolved issues with updates to domain classes
Browse files Browse the repository at this point in the history
Resolved issues with updates to domain classes

Added Identity service
  • Loading branch information
sullivanp-fxl committed Jan 28, 2023
1 parent c252d76 commit 455f6c0
Show file tree
Hide file tree
Showing 142 changed files with 3,659 additions and 471 deletions.
12 changes: 11 additions & 1 deletion .nx-dotnet.rc.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@
"Ocelot.Provider.Consul": "18.0.0",
"Ocelot.Administration": "18.0.0",
"Swashbuckle.AspNetCore": "6.5.0",
"Microsoft.Extensions.Logging": "7.0.0"
"Microsoft.Extensions.Logging": "7.0.0",
"CsvHelper": "30.0.1",
"Microsoft.AspNetCore.Identity": "2.2.0",
"Microsoft.AspNetCore.Authentication": "2.2.0",
"FluentValidation.DependencyInjectionExtensions": "11.4.0",
"AutoMapper.Extensions.Microsoft.DependencyInjection": "12.0.0",
"MediatR.Extensions.Microsoft.DependencyInjection": "11.0.0",
"Microsoft.AspNetCore.ApiAuthorization.IdentityServer": "7.0.2",
"Microsoft.AspNetCore.Identity.EntityFrameworkCore": "7.0.2",
"IdentityServer4": "4.1.2",
"Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore": "7.0.2"
}
}
14 changes: 7 additions & 7 deletions apps/apis/user/Controllers/v1/UsersApi.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* User APIs
*
* A collection of APIs used to get and set user related data
* A collection of APIs used to get and set user related data
*
* The version of the OpenAPI document: 1
* Contact: Patrick.Joseph.Sullivan@protonmail.com
Expand All @@ -25,7 +25,7 @@
using OpenSystem.Core.DotNet.WebApi.Controllers;

namespace OpenSystem.Apis.User.Controllers.v1
{
{

/// <summary>
/// Controller for UsersApi service implementation(s)
Expand Down Expand Up @@ -102,7 +102,7 @@ public async Task<IActionResult> AddUser([FromRoute (Name = "userId")][Required]
// return StatusCode(503, default(ProblemDetailsDto));
string exampleJson = null;
exampleJson = "{\r\n \"guid\" : \"123e4567-e89b-12d3-a456-426614174000\"\r\n}";

var example = exampleJson != null
? JsonSerializer.Deserialize<UpdateSuccessResponseDto>(exampleJson)
: default(UpdateSuccessResponseDto);
Expand Down Expand Up @@ -146,7 +146,7 @@ public async Task<IActionResult> DeleteUser([FromRoute (Name = "userId")][Requir
// return StatusCode(503, default(ProblemDetailsDto));
string exampleJson = null;
exampleJson = "{\r\n \"guid\" : \"123e4567-e89b-12d3-a456-426614174000\"\r\n}";

var example = exampleJson != null
? JsonSerializer.Deserialize<UpdateSuccessResponseDto>(exampleJson)
: default(UpdateSuccessResponseDto);
Expand Down Expand Up @@ -190,7 +190,7 @@ public async Task<IActionResult> GetUser([FromRoute (Name = "userId")][Required]
// return StatusCode(503, default(ProblemDetailsDto));
string exampleJson = null;
exampleJson = "null";

var example = exampleJson != null
? JsonSerializer.Deserialize<UserDto>(exampleJson)
: default(UserDto);
Expand Down Expand Up @@ -235,7 +235,7 @@ public async Task<IActionResult> GetUsers([FromRoute (Name = "userId")][Required
// return StatusCode(503, default(ProblemDetailsDto));
string exampleJson = null;
exampleJson = "null";

var example = exampleJson != null
? JsonSerializer.Deserialize<List<UserDto>>(exampleJson)
: default(List<UserDto>);
Expand Down Expand Up @@ -280,7 +280,7 @@ public async Task<IActionResult> UpdateUser([FromRoute (Name = "userId")][Requir
// return StatusCode(503, default(ProblemDetailsDto));
string exampleJson = null;
exampleJson = "{\r\n \"guid\" : \"123e4567-e89b-12d3-a456-426614174000\"\r\n}";

var example = exampleJson != null
? JsonSerializer.Deserialize<UpdateSuccessResponseDto>(exampleJson)
: default(UpdateSuccessResponseDto);
Expand Down
2 changes: 2 additions & 0 deletions apps/apis/user/OpenSystem.Apis.User.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

<ItemGroup>

<PackageReference Include="Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore" Version="7.0.2" />

<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="7.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.17.0" />
<PackageReference Include="NETStandard.Library" Version="2.0.3" />
Expand Down
23 changes: 22 additions & 1 deletion apps/apis/user/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@
using Microsoft.Extensions.DependencyInjection;
using Serilog;
using OpenSystem.Core.DotNet.Application;
using OpenSystem.Core.DotNet.Infrastructure.Persistence.Contexts;
using OpenSystem.Core.DotNet.Application.Interfaces;
using OpenSystem.Core.DotNet.Infrastructure.Persistence;
using OpenSystem.Core.DotNet.Infrastructure.Extensions;
using OpenSystem.Core.DotNet.Infrastructure;
using OpenSystem.Core.DotNet.WebApi.Constants;
using OpenSystem.Core.DotNet.WebApi.Extensions;
using OpenSystem.Core.DotNet.WebApi.Services;
using OpenSystem.Apis.User.Extensions;

const string SERVICE_NAME = "UserService.Api";
Expand Down Expand Up @@ -42,6 +44,11 @@
builder.Services.AddCorsExtension();
builder.Services.AddHealthChecks();

builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddSingleton<ICurrentUserService,
CurrentUserService>();
builder.Services.AddHttpContextAccessor();

//API Security
builder.Services.AddJWTAuthentication(builder.Configuration);
builder.Services.AddAuthorizationPolicies(builder.Configuration);
Expand All @@ -59,10 +66,13 @@
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error");

// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}

Expand All @@ -76,16 +86,27 @@
// Add this line; you'll need `using Serilog;` up the top, too
app.UseSerilogRequestLogging();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

//Enable CORS
app.UseCors("AllowAll");

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.UseSwaggerExtension();
app.UseErrorHandlingMiddleware();
app.UseHealthChecks("/health-check");
app.MapControllers();

/*app.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");*/

app.MapFallbackToFile("index.html");

app.Run();

Log.Information($"{SERVICE_NAME} has started successfully.");
Expand Down
5 changes: 5 additions & 0 deletions apps/apis/user/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@
"ApplicationName": "Serilog.UserService.Api"
}
},
"IdentityServer": {
"Key": {
"Type": "Development"
}
},
"MailSettings": {
"EmailFrom": "info@janedoe.com",
"SmtpHost": "smtp.janedoe.com",
Expand Down
24 changes: 24 additions & 0 deletions libs/core/dotnet/application/Attributes/AuthorizeAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace OpenSystem.Core.DotNet.Application.Attributes
{
/// <summary>
/// Specifies the class this attribute is applied to requires authorization.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = true)]
public class AuthorizeAttribute : Attribute
{
/// <summary>
/// Initializes a new instance of the <see cref="AuthorizeAttribute"/> class.
/// </summary>
public AuthorizeAttribute() { }

/// <summary>
/// Gets or sets a comma delimited list of roles that are allowed to access the resource.
/// </summary>
public string Roles { get; set; } = string.Empty;

/// <summary>
/// Gets or sets the policy name that determines access to the resource.
/// </summary>
public string Policy { get; set; } = string.Empty;
}
}
89 changes: 89 additions & 0 deletions libs/core/dotnet/application/Behaviors/AuthorizationBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System.Reflection;
using MediatR;
using OpenSystem.Core.DotNet.Application.Attributes;
using OpenSystem.Core.DotNet.Application.Interfaces;
using OpenSystem.Core.DotNet.Domain.Exceptions;

namespace OpenSystem.Core.DotNet.Application.Behaviors
{
public class AuthorizationBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : MediatR.IRequest<TResponse>
{
private readonly ICurrentUserService _currentUserService;

private readonly IIdentityService _identityService;

public AuthorizationBehavior(ICurrentUserService currentUserService,
IIdentityService identityService)
{
_currentUserService = currentUserService;
_identityService = identityService;
}

public async Task<TResponse> Handle(TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
var authorizeAttributes = request.GetType().GetCustomAttributes<AuthorizeAttribute>();

if (authorizeAttributes.Any())
{
// Must be authenticated user
if (_currentUserService.UserId == null)
{
throw new UnauthorizedAccessException();
}

// Role-based authorization
var authorizeAttributesWithRoles = authorizeAttributes.Where(a =>
!string.IsNullOrWhiteSpace(a.Roles));

if (authorizeAttributesWithRoles.Any())
{
var authorized = false;
foreach (var roles in authorizeAttributesWithRoles.Select(a =>
a.Roles.Split(',')))
{
foreach (var role in roles)
{
var isInRole = await _identityService.IsInRoleAsync(_currentUserService.UserId,
role.Trim());
if (isInRole)
{
authorized = true;
break;
}
}
}

// Must be a member of at least one role in roles
if (!authorized)
{
throw new ForbiddenAccessException();
}
}

// Policy-based authorization
var authorizeAttributesWithPolicies = authorizeAttributes.Where(a =>
!string.IsNullOrWhiteSpace(a.Policy));
if (authorizeAttributesWithPolicies.Any())
{
foreach (var policy in authorizeAttributesWithPolicies.Select(a =>
a.Policy))
{
var authorized = await _identityService.AuthorizeAsync(_currentUserService.UserId, policy);

if (!authorized)
{
throw new ForbiddenAccessException();
}
}
}
}

// User is authorized / authorization not required
return await next();
}
}
}
17 changes: 12 additions & 5 deletions libs/core/dotnet/application/Behaviors/LoggingBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,27 @@

namespace OpenSystem.Core.DotNet.Application.Behaviors
{
public class LoggingBehavior<TRequest> : IRequestPreProcessor<TRequest> where TRequest : notnull
{
public class LoggingBehavior<TRequest>
: IRequestPreProcessor<TRequest>
where TRequest : notnull
{
private readonly ILogger _logger;

private readonly ICurrentUserService _currentUserService;

private readonly IIdentityService _identityService;

public LoggingBehavior(ILogger<TRequest> logger, ICurrentUserService currentUserService, IIdentityService identityService)
public LoggingBehavior(ILogger<TRequest> logger,
ICurrentUserService currentUserService,
IIdentityService identityService)
{
_logger = logger;
_currentUserService = currentUserService;
_identityService = identityService;
}

public async Task Process(TRequest request, CancellationToken cancellationToken)
public async Task Process(TRequest request,
CancellationToken cancellationToken)
{
var requestName = typeof(TRequest).Name;
var userId = _currentUserService.UserId ?? string.Empty;
Expand All @@ -34,5 +41,5 @@ public async Task Process(TRequest request, CancellationToken cancellationToken)
userName,
request);
}
}
}
}
67 changes: 67 additions & 0 deletions libs/core/dotnet/application/Behaviors/PerformanceBehavior.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using OpenSystem.Core.DotNet.Application.Interfaces;
using Microsoft.Extensions.Logging;
using System.Diagnostics;
using MediatR;

namespace OpenSystem.Core.DotNet.Application.Behaviors
{
public class PerformanceBehavior<TRequest, TResponse>
: IPipelineBehavior<TRequest, TResponse>
where TRequest : MediatR.IRequest<TResponse>
{
private readonly Stopwatch _timer;

private readonly ILogger<TRequest> _logger;

private readonly ICurrentUserService _currentUserService;

private readonly IIdentityService _identityService;

public PerformanceBehavior(
ILogger<TRequest> logger,
ICurrentUserService currentUserService,
IIdentityService identityService)
{
_timer = new Stopwatch();

_logger = logger;
_currentUserService = currentUserService;
_identityService = identityService;
}

public async Task<TResponse> Handle(TRequest request,
RequestHandlerDelegate<TResponse> next,
CancellationToken cancellationToken)
{
_timer.Start();

var response = await next();

_timer.Stop();

var elapsedMilliseconds = _timer.ElapsedMilliseconds;

if (elapsedMilliseconds > 500)
{
var requestName = typeof(TRequest).Name;
var userId = _currentUserService.UserId ?? string.Empty;
var userName = string.Empty;

if (!string.IsNullOrEmpty(userId))
{
userName = await _identityService.GetUserNameAsync(userId);
}

_logger.LogWarning("CleanArchitecture Long Running Request: {Name} ({ElapsedMilliseconds} milliseconds) {@UserId} {@UserName} {@Request}",
requestName,
elapsedMilliseconds,
userId,
userName,
request);
}

return response;
}
}
}

Loading

0 comments on commit 455f6c0

Please sign in to comment.