-
Notifications
You must be signed in to change notification settings - Fork 393
Expand file tree
/
Copy pathPushedAuthorizationEndpoint.cs
More file actions
165 lines (144 loc) · 6.75 KB
/
PushedAuthorizationEndpoint.cs
File metadata and controls
165 lines (144 loc) · 6.75 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// Copyright (c) Duende Software. All rights reserved.
// See LICENSE in the project root for license information.
#nullable enable
using System.Collections.Specialized;
using System.Net;
using Duende.IdentityModel;
using Duende.IdentityServer.Configuration;
using Duende.IdentityServer.Endpoints.Results;
using Duende.IdentityServer.Extensions;
using Duende.IdentityServer.Hosting;
using Duende.IdentityServer.Licensing.V2;
using Duende.IdentityServer.Logging.Models;
using Duende.IdentityServer.ResponseHandling;
using Duende.IdentityServer.Validation;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace Duende.IdentityServer.Endpoints;
internal class PushedAuthorizationEndpoint : IEndpointHandler
{
private readonly IClientSecretValidator _clientValidator;
private readonly IPushedAuthorizationRequestValidator _parValidator;
private readonly IPushedAuthorizationResponseGenerator _responseGenerator;
private readonly LicenseUsageTracker _features;
private readonly IdentityServerOptions _options;
private readonly ILogger<PushedAuthorizationEndpoint> _logger;
public PushedAuthorizationEndpoint(
IClientSecretValidator clientValidator,
IPushedAuthorizationRequestValidator parValidator,
IPushedAuthorizationResponseGenerator responseGenerator,
LicenseUsageTracker features,
IdentityServerOptions options,
ILogger<PushedAuthorizationEndpoint> logger
)
{
_clientValidator = clientValidator;
_parValidator = parValidator;
_responseGenerator = responseGenerator;
_features = features;
_options = options;
_logger = logger;
}
public async Task<IEndpointResult?> ProcessAsync(HttpContext context)
{
using var activity = Tracing.BasicActivitySource.StartActivity(IdentityServerConstants.EndpointNames.PushedAuthorization);
_logger.LogDebug("Start pushed authorization request");
_features.FeatureUsed(LicenseFeature.PAR);
NameValueCollection values;
if (HttpMethods.IsPost(context.Request.Method))
{
var form = await context.Request.ReadFormAsync();
values = form.AsNameValueCollection();
}
else
{
return new StatusCodeResult(HttpStatusCode.MethodNotAllowed);
}
// Authenticate Client
var client = await _clientValidator.ValidateAsync(context);
if (client.IsError)
{
return CreateErrorResult(
logMessage: "Client secret validation failed",
error: client.Error ?? OidcConstants.AuthorizeErrors.InvalidRequest,
errorDescription: client.ErrorDescription);
}
var validationContext = new PushedAuthorizationRequestValidationContext(values, client.Client);
if (context.Request.Headers.TryGetValue(OidcConstants.HttpHeaders.DPoP, out var dpopHeader))
{
if (dpopHeader.Count > 1)
{
return CreateErrorResult(
logMessage: "Too many DPoP headers provided.",
error: OidcConstants.AuthorizeErrors.InvalidRequest);
}
validationContext.DPoPProofToken = dpopHeader.First();
//Note: if the client authenticated with mTLS, we need to know to properly validate the htu of the DPoP proof token
validationContext.ClientCertificate = await context.Connection.GetClientCertificateAsync();
}
// Perform validations specific to PAR, as well as validation of the pushed parameters
var parValidationResult = await _parValidator.ValidateAsync(validationContext);
if (parValidationResult.IsError)
{
return CreateErrorResult(
logMessage: "Pushed authorization validation failed",
request: parValidationResult.ValidatedRequest,
serverNonce: parValidationResult.ServerIssuedNonce,
error: parValidationResult.Error ?? OidcConstants.AuthorizeErrors.InvalidRequest,
errorDescription: parValidationResult.ErrorDescription);
}
// This "can't happen", because PAR validation results don't have a constructor that
// allows you to create a successful result without a validated request, but static analysis
// doesn't know that.
if (parValidationResult.ValidatedRequest is null)
{
throw new InvalidOperationException("Invalid PAR validation result: success without a validated request");
}
var response = await _responseGenerator.CreateResponseAsync(parValidationResult.ValidatedRequest);
switch (response)
{
case PushedAuthorizationSuccess success:
Telemetry.Metrics.PushedAuthorizationRequest(parValidationResult.ValidatedRequest.Client.ClientId);
return new PushedAuthorizationResult(success);
case PushedAuthorizationFailure fail:
Telemetry.Metrics.PushedAuthorizationRequestFailure(parValidationResult.ValidatedRequest.ClientId, fail.Error);
return new PushedAuthorizationErrorResult(fail);
default:
throw new Exception("Unexpected pushed authorization response. The result of the pushed authorization response generator should be either a PushedAuthorizationSuccess or PushedAuthorizationFailure.");
}
}
private PushedAuthorizationErrorResult CreateErrorResult(
string logMessage,
ValidatedPushedAuthorizationRequest? request = null,
string? serverNonce = null,
string error = OidcConstants.AuthorizeErrors.ServerError,
string? errorDescription = null,
bool logError = true)
{
if (logError)
{
_logger.LogError(logMessage);
}
if (request != null)
{
var details = new AuthorizeRequestValidationLog(request, _options.Logging.PushedAuthorizationSensitiveValuesFilter);
_logger.LogInformation("{@validationDetails}", details);
}
// Note: this is an expected case in the normal DPoP flow and is not a real failure event.
// Keeping a debug log to help with troubleshooting in the case of a buggy client.
if (serverNonce != null)
{
_logger.LogDebug("Pushed authorization request returned an error with a server issued nonce. This is an expected event when using DPoP server nonces.");
}
else
{
Telemetry.Metrics.PushedAuthorizationRequestFailure(request?.Client.ClientId, logMessage);
}
return new PushedAuthorizationErrorResult(new PushedAuthorizationFailure
{
Error = error,
ErrorDescription = errorDescription,
DPoPNonce = serverNonce
});
}
}