Skip to content

Commit acf1b27

Browse files
committed
Create ProblemDetails ResponseWriter
1 parent a80280d commit acf1b27

File tree

8 files changed

+40
-81
lines changed

8 files changed

+40
-81
lines changed

Common/Authentication/AuthenticationHandlers/ApiTokenAuthentication.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.Net.Mime;
2-
using Microsoft.AspNetCore.Authentication;
3-
using Microsoft.AspNetCore.Http.Json;
1+
using Microsoft.AspNetCore.Authentication;
42
using Microsoft.EntityFrameworkCore;
53
using Microsoft.Extensions.Options;
64
using OpenShock.Common.Authentication.Services;
@@ -12,7 +10,6 @@
1210
using OpenShock.Common.Utils;
1311
using System.Security.Claims;
1412
using System.Text.Encodings.Web;
15-
using System.Text.Json;
1613

1714
namespace OpenShock.Common.Authentication.AuthenticationHandlers;
1815

@@ -22,7 +19,6 @@ public sealed class ApiTokenAuthentication : AuthenticationHandler<Authenticatio
2219
private readonly IUserReferenceService _userReferenceService;
2320
private readonly IBatchUpdateService _batchUpdateService;
2421
private readonly OpenShockContext _db;
25-
private readonly JsonSerializerOptions _serializerOptions;
2622
private OpenShockProblem? _authResultError = null;
2723

2824
public ApiTokenAuthentication(
@@ -32,13 +28,13 @@ public ApiTokenAuthentication(
3228
IClientAuthService<User> clientAuth,
3329
IUserReferenceService userReferenceService,
3430
OpenShockContext db,
35-
IOptions<JsonOptions> jsonOptions, IBatchUpdateService batchUpdateService)
31+
IBatchUpdateService batchUpdateService
32+
)
3633
: base(options, logger, encoder)
3734
{
3835
_authService = clientAuth;
3936
_userReferenceService = userReferenceService;
4037
_db = db;
41-
_serializerOptions = jsonOptions.Value.SerializerOptions;
4238
_batchUpdateService = batchUpdateService;
4339
}
4440

@@ -95,8 +91,6 @@ protected override Task HandleChallengeAsync(AuthenticationProperties properties
9591
{
9692
if (Context.Response.HasStarted) return Task.CompletedTask;
9793
_authResultError ??= AuthResultError.UnknownError;
98-
Response.StatusCode = _authResultError.Status!.Value;
99-
_authResultError.AddContext(Context);
100-
return Context.Response.WriteAsJsonAsync(_authResultError, _serializerOptions, contentType: MediaTypeNames.Application.ProblemJson);
94+
return _authResultError.WriteAsJsonAsync(Context);
10195
}
10296
}

Common/Authentication/AuthenticationHandlers/HubAuthentication.cs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
using System.Net.Mime;
2-
using System.Security.Claims;
1+
using System.Security.Claims;
32
using System.Text.Encodings.Web;
4-
using System.Text.Json;
53
using Microsoft.AspNetCore.Authentication;
6-
using Microsoft.AspNetCore.Http.Json;
74
using Microsoft.EntityFrameworkCore;
85
using Microsoft.Extensions.Options;
96
using OpenShock.Common.Authentication.Services;
@@ -22,7 +19,6 @@ public sealed class HubAuthentication : AuthenticationHandler<AuthenticationSche
2219
private readonly IClientAuthService<Device> _authService;
2320
private readonly OpenShockContext _db;
2421

25-
private readonly JsonSerializerOptions _serializerOptions;
2622
private OpenShockProblem? _authResultError = null;
2723

2824

@@ -31,13 +27,12 @@ public HubAuthentication(
3127
ILoggerFactory logger,
3228
UrlEncoder encoder,
3329
IClientAuthService<Device> clientAuth,
34-
OpenShockContext db,
35-
IOptions<JsonOptions> jsonOptions)
30+
OpenShockContext db
31+
)
3632
: base(options, logger, encoder)
3733
{
3834
_authService = clientAuth;
3935
_db = db;
40-
_serializerOptions = jsonOptions.Value.SerializerOptions;
4136
}
4237

4338
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
@@ -83,8 +78,6 @@ protected override Task HandleChallengeAsync(AuthenticationProperties properties
8378
{
8479
if (Context.Response.HasStarted) return Task.CompletedTask;
8580
_authResultError ??= AuthResultError.UnknownError;
86-
Response.StatusCode = _authResultError.Status!.Value;
87-
_authResultError.AddContext(Context);
88-
return Context.Response.WriteAsJsonAsync(_authResultError, _serializerOptions, contentType: MediaTypeNames.Application.ProblemJson);
81+
return _authResultError.WriteAsJsonAsync(Context);
8982
}
9083
}

Common/Authentication/AuthenticationHandlers/UserSessionAuthentication.cs

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.Net.Mime;
2-
using Microsoft.AspNetCore.Authentication;
3-
using Microsoft.AspNetCore.Http.Json;
1+
using Microsoft.AspNetCore.Authentication;
42
using Microsoft.EntityFrameworkCore;
53
using Microsoft.Extensions.Options;
64
using OpenShock.Common.Authentication.Services;
@@ -13,7 +11,6 @@
1311
using OpenShock.Common.Services.Session;
1412
using System.Security.Claims;
1513
using System.Text.Encodings.Web;
16-
using System.Text.Json;
1714

1815
namespace OpenShock.Common.Authentication.AuthenticationHandlers;
1916

@@ -24,7 +21,6 @@ public sealed class UserSessionAuthentication : AuthenticationHandler<Authentica
2421
private readonly IBatchUpdateService _batchUpdateService;
2522
private readonly OpenShockContext _db;
2623
private readonly ISessionService _sessionService;
27-
private readonly JsonSerializerOptions _serializerOptions;
2824
private OpenShockProblem? _authResultError = null;
2925

3026
public UserSessionAuthentication(
@@ -35,14 +31,14 @@ public UserSessionAuthentication(
3531
IUserReferenceService userReferenceService,
3632
OpenShockContext db,
3733
ISessionService sessionService,
38-
IOptions<JsonOptions> jsonOptions, IBatchUpdateService batchUpdateService)
34+
IBatchUpdateService batchUpdateService
35+
)
3936
: base(options, logger, encoder)
4037
{
4138
_authService = clientAuth;
4239
_userReferenceService = userReferenceService;
4340
_db = db;
4441
_sessionService = sessionService;
45-
_serializerOptions = jsonOptions.Value.SerializerOptions;
4642
_batchUpdateService = batchUpdateService;
4743
}
4844

@@ -109,8 +105,6 @@ protected override Task HandleChallengeAsync(AuthenticationProperties properties
109105
{
110106
if (Context.Response.HasStarted) return Task.CompletedTask;
111107
_authResultError ??= AuthResultError.UnknownError;
112-
Response.StatusCode = _authResultError.Status!.Value;
113-
_authResultError.AddContext(Context);
114-
return Context.Response.WriteAsJsonAsync(_authResultError, _serializerOptions, contentType: MediaTypeNames.Application.ProblemJson);
108+
return _authResultError.WriteAsJsonAsync(Context);
115109
}
116110
}

Common/Authentication/OpenShockAuthorizationMiddlewareResultHandler.cs

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,15 @@
1-
using System.Net.Mime;
2-
using System.Text.Json;
3-
using Microsoft.AspNetCore.Authorization;
1+
using Microsoft.AspNetCore.Authorization;
42
using Microsoft.AspNetCore.Authorization.Policy;
5-
using Microsoft.AspNetCore.Http.Json;
6-
using Microsoft.Extensions.Options;
73
using OpenShock.Common.Errors;
84

95
namespace OpenShock.Common.Authentication;
106

117
public class OpenShockAuthorizationMiddlewareResultHandler : IAuthorizationMiddlewareResultHandler
128
{
13-
private readonly JsonSerializerOptions _serializerOptions;
149
private readonly AuthorizationMiddlewareResultHandler _defaultHandler;
1510

16-
public OpenShockAuthorizationMiddlewareResultHandler(IOptions<JsonOptions> jsonOptions)
11+
public OpenShockAuthorizationMiddlewareResultHandler()
1712
{
18-
_serializerOptions = jsonOptions.Value.SerializerOptions;
1913
_defaultHandler = new AuthorizationMiddlewareResultHandler();
2014
}
2115

@@ -25,10 +19,7 @@ public Task HandleAsync(RequestDelegate next, HttpContext context, Authorization
2519
if (authorizeResult.Forbidden)
2620
{
2721
var failedRequirements = authorizeResult.AuthorizationFailure?.FailedRequirements.Select(x => x.ToString() ?? "error") ?? [];
28-
var problem = AuthorizationError.PolicyNotMet(failedRequirements);
29-
context.Response.StatusCode = problem.Status!.Value;
30-
problem.AddContext(context);
31-
return context.Response.WriteAsJsonAsync(problem, _serializerOptions, contentType: MediaTypeNames.Application.ProblemJson);
22+
return AuthorizationError.PolicyNotMet(failedRequirements).WriteAsJsonAsync(context);
3223
}
3324

3425
return _defaultHandler.HandleAsync(next, context, policy, authorizeResult);

Common/ExceptionHandle/OpenShockExceptionHandler.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,9 @@ public async ValueTask<bool> TryHandleAsync(HttpContext context, Exception excep
2424
{
2525
await PrintRequestInfo(context);
2626
}
27-
28-
var responseObject = ExceptionError.Exception;
29-
responseObject.AddContext(context);
3027

31-
return await _problemDetailsService.TryWriteAsync(new ProblemDetailsContext
32-
{
33-
HttpContext = context,
34-
Exception = exception,
35-
ProblemDetails = responseObject
36-
});
28+
await ExceptionError.Exception.WriteAsJsonAsync(context, cancellationToken);
29+
return context.Response.HasStarted;
3730
}
3831

3932
private async Task PrintRequestInfo(HttpContext context)

Common/JsonSerialization/JsonSettings.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,11 @@ public static void MvcOptions(Microsoft.AspNetCore.Mvc.JsonOptions options)
3838
PropertyNameCaseInsensitive = true,
3939
Converters = { new FlagCompatibleJsonStringEnumConverter() }
4040
};
41+
42+
public static readonly JsonSerializerOptions ProblemDetailsSettings = new() // TODO: Identical to MvcOptions
43+
{
44+
PropertyNameCaseInsensitive = true,
45+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
46+
Converters = { new PermissionTypeConverter(), new FlagCompatibleJsonStringEnumConverter() }
47+
};
4148
}

Common/Problems/OpenShockProblem.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System.Net;
2+
using System.Net.Mime;
23
using Microsoft.AspNetCore.Mvc;
4+
using OpenShock.Common.JsonSerialization;
35

46
namespace OpenShock.Common.Problems;
57

@@ -27,19 +29,25 @@ private OpenShockProblem(string type, string title, int status = 400, string? de
2729
[Obsolete("This is the exact same as requestId, refer to using requestId in the future")]
2830
public string? TraceId => RequestId;
2931

30-
public string? RequestId { get; set; }
32+
public string? RequestId { get; private set; }
3133

32-
public ObjectResult ToObjectResult(HttpContext httpContext)
34+
public ObjectResult ToObjectResult(HttpContext context)
3335
{
34-
AddContext(httpContext);
36+
RequestId = context.TraceIdentifier;
37+
3538
return new ObjectResult(this)
3639
{
3740
StatusCode = Status
3841
};
3942
}
4043

41-
public void AddContext(HttpContext httpContext)
44+
public Task WriteAsJsonAsync(HttpContext context, CancellationToken cancellationToken)
4245
{
43-
RequestId = httpContext.TraceIdentifier;
46+
context.Response.StatusCode = Status ?? StatusCodes.Status400BadRequest;
47+
RequestId = context.TraceIdentifier;
48+
49+
return context.Response.WriteAsJsonAsync(this, JsonSettings.ProblemDetailsSettings, MediaTypeNames.Application.ProblemJson, cancellationToken);
4450
}
51+
52+
public Task WriteAsJsonAsync(HttpContext context) => WriteAsJsonAsync(context, context.RequestAborted);
4553
}

Common/Websocket/WebsockBaseController.cs

Lines changed: 3 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
1-
using System.Net.Mime;
2-
using System.Net.WebSockets;
1+
using System.Net.WebSockets;
32
using System.Text.Json;
43
using System.Threading.Channels;
54
using Microsoft.AspNetCore.Mvc;
6-
using Microsoft.Extensions.Options;
75
using OneOf;
86
using OneOf.Types;
97
using OpenShock.Common.Errors;
108
using OpenShock.Common.Problems;
119
using OpenShock.Common.Utils;
12-
using JsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions;
1310

1411
namespace OpenShock.Common.Websocket;
1512

@@ -118,32 +115,14 @@ public async Task Get([FromServices] IHostApplicationLifetime lifetime, Cancella
118115

119116
if (!HttpContext.WebSockets.IsWebSocketRequest)
120117
{
121-
var jsonOptions = HttpContext.RequestServices.GetRequiredService<IOptions<JsonOptions>>();
122-
HttpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
123-
var response = WebsocketError.NonWebsocketRequest;
124-
response.AddContext(HttpContext);
125-
// ReSharper disable once MethodSupportsCancellation
126-
await HttpContext.Response.WriteAsJsonAsync(
127-
response,
128-
jsonOptions.Value.SerializerOptions,
129-
contentType: MediaTypeNames.Application.ProblemJson,
130-
cancellationToken: cancellationToken);
118+
await WebsocketError.NonWebsocketRequest.WriteAsJsonAsync(HttpContext, LinkedToken);
131119
return;
132120
}
133121

134122
var connectionPrecondition = await ConnectionPrecondition();
135123
if (connectionPrecondition.IsT1)
136124
{
137-
var jsonOptions = HttpContext.RequestServices.GetRequiredService<IOptions<JsonOptions>>();
138-
var response = connectionPrecondition.AsT1.Value;
139-
HttpContext.Response.StatusCode = response.Status ?? StatusCodes.Status400BadRequest;
140-
response.AddContext(HttpContext);
141-
// ReSharper disable once MethodSupportsCancellation
142-
await HttpContext.Response.WriteAsJsonAsync(
143-
response,
144-
jsonOptions.Value.SerializerOptions,
145-
contentType: MediaTypeNames.Application.ProblemJson,
146-
cancellationToken: cancellationToken);
125+
await connectionPrecondition.AsT1.Value.WriteAsJsonAsync(HttpContext, LinkedToken);
147126
return;
148127
}
149128

0 commit comments

Comments
 (0)