Skip to content

Commit 706f288

Browse files
Replace OidcActionResult with per-endpoint specific result types
Agent-Logs-Url: https://github.com/TransactionProcessing/SecurityService/sessions/466bce18-813c-430d-8157-8c1fc9758113 Co-authored-by: StuartFerguson <16325469+StuartFerguson@users.noreply.github.com>
1 parent 1f034a6 commit 706f288

6 files changed

Lines changed: 150 additions & 85 deletions

File tree

SecurityService.BusinessLogic/Oidc/OidcActionResult.cs

Lines changed: 0 additions & 20 deletions
This file was deleted.

SecurityService.BusinessLogic/Oidc/OidcCommands.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ namespace SecurityService.BusinessLogic.Oidc;
66

77
public static class OidcCommands
88
{
9-
public sealed record AuthorizeCommand(HttpContext HttpContext) : IRequest<Result<OidcActionResult>>;
10-
public sealed record TokenCommand(HttpContext HttpContext) : IRequest<Result<OidcActionResult>>;
11-
public sealed record LogoutCommand(HttpContext HttpContext) : IRequest<Result<OidcActionResult>>;
12-
public sealed record UserInfoCommand(HttpContext HttpContext) : IRequest<Result<OidcActionResult>>;
9+
public sealed record AuthorizeCommand(HttpContext HttpContext) : IRequest<Result<AuthorizeCommandResult>>;
10+
public sealed record TokenCommand(HttpContext HttpContext) : IRequest<Result<TokenCommandResult>>;
11+
public sealed record LogoutCommand(HttpContext HttpContext) : IRequest<Result<LogoutCommandResult>>;
12+
public sealed record UserInfoCommand(HttpContext HttpContext) : IRequest<Result<UserInfoCommandResult>>;
1313
}

SecurityService.BusinessLogic/Oidc/OidcRequestHandler.cs

Lines changed: 46 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
namespace SecurityService.BusinessLogic.Oidc;
1818

1919
public sealed class OidcRequestHandler :
20-
IRequestHandler<OidcCommands.AuthorizeCommand, Result<OidcActionResult>>,
21-
IRequestHandler<OidcCommands.TokenCommand, Result<OidcActionResult>>,
22-
IRequestHandler<OidcCommands.LogoutCommand, Result<OidcActionResult>>,
23-
IRequestHandler<OidcCommands.UserInfoCommand, Result<OidcActionResult>>
20+
IRequestHandler<OidcCommands.AuthorizeCommand, Result<AuthorizeCommandResult>>,
21+
IRequestHandler<OidcCommands.TokenCommand, Result<TokenCommandResult>>,
22+
IRequestHandler<OidcCommands.LogoutCommand, Result<LogoutCommandResult>>,
23+
IRequestHandler<OidcCommands.UserInfoCommand, Result<UserInfoCommandResult>>
2424
{
2525
private readonly UserManager<ApplicationUser> _userManager;
2626
private readonly SignInManager<ApplicationUser> _signInManager;
@@ -45,7 +45,7 @@ public OidcRequestHandler(
4545
this._dbContext = dbContext;
4646
}
4747

48-
public async Task<Result<OidcActionResult>> Handle(OidcCommands.AuthorizeCommand command, CancellationToken cancellationToken)
48+
public async Task<Result<AuthorizeCommandResult>> Handle(OidcCommands.AuthorizeCommand command, CancellationToken cancellationToken)
4949
{
5050
var context = command.HttpContext;
5151
var request = context.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
@@ -54,106 +54,106 @@ public async Task<Result<OidcActionResult>> Handle(OidcCommands.AuthorizeCommand
5454
var authenticationResult = await context.AuthenticateAsync(IdentityConstants.ApplicationScheme);
5555
if (authenticationResult.Succeeded == false)
5656
{
57-
return Result.Success<OidcActionResult>(HandleAuthenticationFailed(request, currentRequestUrl));
57+
return Result.Success<AuthorizeCommandResult>(HandleAuthenticationFailed(request, currentRequestUrl));
5858
}
5959

6060
var user = await this._userManager.GetUserAsync(authenticationResult.Principal);
6161
if (user is null)
6262
{
63-
return Result.Success<OidcActionResult>(ForbidServer(Errors.LoginRequired, "The user account could not be resolved."));
63+
return Result.Success<AuthorizeCommandResult>(AuthorizeForbidServer(Errors.LoginRequired, "The user account could not be resolved."));
6464
}
6565

6666
var application = await this._applicationManager.FindByClientIdAsync(request.ClientId!, cancellationToken);
6767
if (application is null)
6868
{
69-
return Result.Success<OidcActionResult>(new OidcBadRequestResult(new { error = "invalid_client", error_description = "The client application could not be found." }));
69+
return Result.Success<AuthorizeCommandResult>(new AuthorizeBadRequestResult(new { error = "invalid_client", error_description = "The client application could not be found." }));
7070
}
7171

7272
if (context.Request.Query.TryGetValue("consent", out var consentDecision))
7373
{
74-
return Result.Success<OidcActionResult>(await this.HandleConsentDecision(user, request, application, context, consentDecision!, cancellationToken));
74+
return Result.Success<AuthorizeCommandResult>(await this.HandleConsentDecision(user, request, application, context, consentDecision!, cancellationToken));
7575
}
7676

77-
return Result.Success<OidcActionResult>(await this.HandleConsentType(user, request, application, currentRequestUrl, cancellationToken));
77+
return Result.Success<AuthorizeCommandResult>(await this.HandleConsentType(user, request, application, currentRequestUrl, cancellationToken));
7878
}
7979

80-
public async Task<Result<OidcActionResult>> Handle(OidcCommands.TokenCommand command, CancellationToken cancellationToken)
80+
public async Task<Result<TokenCommandResult>> Handle(OidcCommands.TokenCommand command, CancellationToken cancellationToken)
8181
{
8282
var context = command.HttpContext;
8383
var request = context.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("The OpenID Connect request cannot be retrieved.");
8484

8585
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType() || request.IsDeviceCodeGrantType())
8686
{
87-
return Result.Success<OidcActionResult>(await this.HandleCodeOrRefreshToken(context, cancellationToken));
87+
return Result.Success<TokenCommandResult>(await this.HandleCodeOrRefreshToken(context, cancellationToken));
8888
}
8989

9090
if (request.IsClientCredentialsGrantType())
9191
{
92-
return Result.Success<OidcActionResult>(await this.HandleClientCredentials(request, cancellationToken));
92+
return Result.Success<TokenCommandResult>(await this.HandleClientCredentials(request, cancellationToken));
9393
}
9494

9595
if (request.IsPasswordGrantType())
9696
{
97-
return Result.Success<OidcActionResult>(await this.HandlePasswordGrant(request, cancellationToken));
97+
return Result.Success<TokenCommandResult>(await this.HandlePasswordGrant(request, cancellationToken));
9898
}
9999

100-
return Result.Success<OidcActionResult>(new OidcBadRequestResult(new { error = "unsupported_grant_type", error_description = "The specified grant type is not supported by this service." }));
100+
return Result.Success<TokenCommandResult>(new TokenBadRequestResult(new { error = "unsupported_grant_type", error_description = "The specified grant type is not supported by this service." }));
101101
}
102102

103-
public Task<Result<OidcActionResult>> Handle(OidcCommands.LogoutCommand command, CancellationToken cancellationToken)
103+
public Task<Result<LogoutCommandResult>> Handle(OidcCommands.LogoutCommand command, CancellationToken cancellationToken)
104104
{
105105
var context = command.HttpContext;
106106
var currentRequestUrl = OidcHelpers.BuildCurrentRequestUrl(context.Request);
107107
var confirmed = string.Equals(context.Request.Query["logout"], "confirmed", StringComparison.OrdinalIgnoreCase);
108108
if (confirmed)
109109
{
110-
OidcActionResult signOut = new OidcSignOutResult(
110+
LogoutCommandResult signOut = new LogoutSignOutResult(
111111
new AuthenticationProperties { RedirectUri = "/Account/Logout/LoggedOut" },
112112
[IdentityConstants.ApplicationScheme, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme]);
113113
return Task.FromResult(Result.Success(signOut));
114114
}
115115

116-
OidcActionResult redirect = new OidcRedirectResult($"/Account/Logout?returnUrl={Uri.EscapeDataString(currentRequestUrl)}");
116+
LogoutCommandResult redirect = new LogoutRedirectResult($"/Account/Logout?returnUrl={Uri.EscapeDataString(currentRequestUrl)}");
117117
return Task.FromResult(Result.Success(redirect));
118118
}
119119

120-
public async Task<Result<OidcActionResult>> Handle(OidcCommands.UserInfoCommand command, CancellationToken cancellationToken)
120+
public async Task<Result<UserInfoCommandResult>> Handle(OidcCommands.UserInfoCommand command, CancellationToken cancellationToken)
121121
{
122122
var context = command.HttpContext;
123123
var authenticationResult = await context.AuthenticateAsync(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
124124
if (authenticationResult.Succeeded == false || authenticationResult.Principal is null)
125125
{
126-
return Result.Success<OidcActionResult>(InvalidToken("The access token is missing or invalid."));
126+
return Result.Success<UserInfoCommandResult>(InvalidToken("The access token is missing or invalid."));
127127
}
128128

129129
var subject = authenticationResult.Principal.GetClaim(Claims.Subject);
130130
if (string.IsNullOrWhiteSpace(subject))
131131
{
132-
return Result.Success<OidcActionResult>(InvalidToken("The access token does not contain a subject identifier."));
132+
return Result.Success<UserInfoCommandResult>(InvalidToken("The access token does not contain a subject identifier."));
133133
}
134134

135135
var user = await this._userManager.FindByIdAsync(subject);
136136
if (user is null)
137137
{
138-
return Result.Success<OidcActionResult>(InvalidToken("The user associated with the token could not be found."));
138+
return Result.Success<UserInfoCommandResult>(InvalidToken("The user associated with the token could not be found."));
139139
}
140140

141141
var scopes = authenticationResult.Principal.GetScopes().ToHashSet(StringComparer.OrdinalIgnoreCase);
142142
var response = await this.BuildUserInfoResponse(user, scopes);
143-
return Result.Success<OidcActionResult>(new OidcJsonResult(response.Where(pair => pair.Value is not null).ToDictionary(pair => pair.Key, pair => pair.Value)));
143+
return Result.Success<UserInfoCommandResult>(new UserInfoJsonResult(response.Where(pair => pair.Value is not null).ToDictionary(pair => pair.Key, pair => pair.Value)));
144144
}
145145

146-
private static OidcActionResult HandleAuthenticationFailed(OpenIddictRequest request, string currentRequestUrl)
146+
private static AuthorizeCommandResult HandleAuthenticationFailed(OpenIddictRequest request, string currentRequestUrl)
147147
{
148148
if (request.HasPromptValue(PromptValues.None))
149149
{
150-
return ForbidServer(Errors.LoginRequired, "The user is not logged in.");
150+
return AuthorizeForbidServer(Errors.LoginRequired, "The user is not logged in.");
151151
}
152152

153-
return new OidcChallengeResult(new AuthenticationProperties { RedirectUri = currentRequestUrl }, [IdentityConstants.ApplicationScheme]);
153+
return new AuthorizeChallengeResult(new AuthenticationProperties { RedirectUri = currentRequestUrl }, [IdentityConstants.ApplicationScheme]);
154154
}
155155

156-
private async Task<OidcActionResult> HandleConsentDecision(
156+
private async Task<AuthorizeCommandResult> HandleConsentDecision(
157157
ApplicationUser user,
158158
OpenIddictRequest request,
159159
object application,
@@ -163,7 +163,7 @@ private async Task<OidcActionResult> HandleConsentDecision(
163163
{
164164
if (string.Equals(consentDecision, "denied", StringComparison.OrdinalIgnoreCase))
165165
{
166-
return ForbidServer(Errors.AccessDenied, "The authorization request was denied.");
166+
return AuthorizeForbidServer(Errors.AccessDenied, "The authorization request was denied.");
167167
}
168168

169169
if (string.Equals(consentDecision, "accepted", StringComparison.OrdinalIgnoreCase))
@@ -174,7 +174,7 @@ private async Task<OidcActionResult> HandleConsentDecision(
174174
return await this.HandleConsentType(user, request, application, OidcHelpers.BuildCurrentRequestUrl(context.Request), cancellationToken);
175175
}
176176

177-
private async Task<OidcActionResult> HandleConsentType(
177+
private async Task<AuthorizeCommandResult> HandleConsentType(
178178
ApplicationUser user,
179179
OpenIddictRequest request,
180180
object application,
@@ -194,16 +194,16 @@ private async Task<OidcActionResult> HandleConsentType(
194194
{
195195
if (request.HasPromptValue(PromptValues.None))
196196
{
197-
return ForbidServer(Errors.ConsentRequired, "Interactive user consent is required.");
197+
return AuthorizeForbidServer(Errors.ConsentRequired, "Interactive user consent is required.");
198198
}
199199

200-
return new OidcRedirectResult($"/Consent?returnUrl={Uri.EscapeDataString(currentRequestUrl)}");
200+
return new AuthorizeRedirectResult($"/Consent?returnUrl={Uri.EscapeDataString(currentRequestUrl)}");
201201
}
202202

203203
return await this.CompleteAuthorization(user, request, application, cancellationToken, request.GetScopes());
204204
}
205205

206-
private async Task<OidcActionResult> HandleCodeOrRefreshToken(HttpContext context, CancellationToken cancellationToken)
206+
private async Task<TokenCommandResult> HandleCodeOrRefreshToken(HttpContext context, CancellationToken cancellationToken)
207207
{
208208
var authenticationResult = await context.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
209209
if (authenticationResult.Succeeded == false || authenticationResult.Principal is null)
@@ -214,7 +214,7 @@ private async Task<OidcActionResult> HandleCodeOrRefreshToken(HttpContext contex
214214
var subject = authenticationResult.Principal.GetClaim(Claims.Subject);
215215
if (string.IsNullOrWhiteSpace(subject))
216216
{
217-
return new OidcSignInResult(authenticationResult.Principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
217+
return new TokenSignInResult(authenticationResult.Principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
218218
}
219219

220220
var user = await this._userManager.FindByIdAsync(subject);
@@ -226,10 +226,10 @@ private async Task<OidcActionResult> HandleCodeOrRefreshToken(HttpContext contex
226226
var scopes = authenticationResult.Principal.GetScopes();
227227
var resources = await this._scopeManager.ListResourcesAsync(ImmutableArray.CreateRange(scopes), cancellationToken).ToListAsync(cancellationToken);
228228
var principal = await OidcHelpers.CreatePrincipal(user, this._userManager, scopes, resources, authenticationResult.Principal.GetAuthorizationId());
229-
return new OidcSignInResult(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
229+
return new TokenSignInResult(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
230230
}
231231

232-
private async Task<OidcActionResult> HandleClientCredentials(OpenIddictRequest request, CancellationToken cancellationToken)
232+
private async Task<TokenCommandResult> HandleClientCredentials(OpenIddictRequest request, CancellationToken cancellationToken)
233233
{
234234
var grantedScopes = await OidcHelpers.ResolveClientCredentialsScopes(request, this._dbContext, cancellationToken);
235235
var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType, Claims.Name, Claims.Role);
@@ -238,10 +238,10 @@ private async Task<OidcActionResult> HandleClientCredentials(OpenIddictRequest r
238238
identity.SetScopes(grantedScopes);
239239
identity.SetResources(await this._scopeManager.ListResourcesAsync(ImmutableArray.CreateRange(grantedScopes), cancellationToken).ToListAsync(cancellationToken));
240240
identity.SetDestinations(OidcHelpers.GetDestinations);
241-
return new OidcSignInResult(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
241+
return new TokenSignInResult(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
242242
}
243243

244-
private async Task<OidcActionResult> HandlePasswordGrant(OpenIddictRequest request, CancellationToken cancellationToken)
244+
private async Task<TokenCommandResult> HandlePasswordGrant(OpenIddictRequest request, CancellationToken cancellationToken)
245245
{
246246
var user = await this._userManager.FindByNameAsync(request.Username!);
247247
if (user is null)
@@ -258,7 +258,7 @@ private async Task<OidcActionResult> HandlePasswordGrant(OpenIddictRequest reque
258258
var grantedScopes = await OidcHelpers.ResolveClientCredentialsScopes(request, this._dbContext, cancellationToken);
259259
var resources = await this._scopeManager.ListResourcesAsync(ImmutableArray.CreateRange(grantedScopes), cancellationToken).ToListAsync(cancellationToken);
260260
var principal = await OidcHelpers.CreatePrincipal(user, this._userManager, grantedScopes, resources, authorizationId: null);
261-
return new OidcSignInResult(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
261+
return new TokenSignInResult(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
262262
}
263263

264264
private async Task<Dictionary<string, object?>> BuildUserInfoResponse(ApplicationUser user, HashSet<string> scopes)
@@ -296,16 +296,16 @@ private async Task<OidcActionResult> HandlePasswordGrant(OpenIddictRequest reque
296296
return response;
297297
}
298298

299-
private static OidcForbidResult ForbidServer(string error, string description) =>
300-
new OidcForbidResult(
299+
private static AuthorizeForbidResult AuthorizeForbidServer(string error, string description) =>
300+
new AuthorizeForbidResult(
301301
new AuthenticationProperties(new Dictionary<string, string?>
302302
{
303303
[OpenIddictServerAspNetCoreConstants.Properties.Error] = error,
304304
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = description
305305
}),
306306
[OpenIddictServerAspNetCoreDefaults.AuthenticationScheme]);
307307

308-
private async Task<OidcActionResult> CompleteAuthorization(
308+
private async Task<AuthorizeCommandResult> CompleteAuthorization(
309309
ApplicationUser user,
310310
OpenIddictRequest request,
311311
object application,
@@ -333,20 +333,20 @@ private async Task<OidcActionResult> CompleteAuthorization(
333333
cancellationToken: cancellationToken);
334334

335335
principal.SetAuthorizationId(await this._authorizationManager.GetIdAsync(authorization, cancellationToken));
336-
return new OidcSignInResult(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
336+
return new AuthorizeSignInResult(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme);
337337
}
338338

339-
private static OidcForbidResult InvalidGrant(string description = "The username/password couple is invalid.") =>
340-
new OidcForbidResult(
339+
private static TokenForbidResult InvalidGrant(string description = "The username/password couple is invalid.") =>
340+
new TokenForbidResult(
341341
new AuthenticationProperties(new Dictionary<string, string?>
342342
{
343343
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
344344
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = description
345345
}),
346346
[OpenIddictServerAspNetCoreDefaults.AuthenticationScheme]);
347347

348-
private static OidcChallengeResult InvalidToken(string description) =>
349-
new OidcChallengeResult(
348+
private static UserInfoChallengeResult InvalidToken(string description) =>
349+
new UserInfoChallengeResult(
350350
new AuthenticationProperties(new Dictionary<string, string?>
351351
{
352352
[OpenIddictValidationAspNetCoreConstants.Properties.Error] = Errors.InvalidToken,

0 commit comments

Comments
 (0)