This repository was archived by the owner on Feb 11, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 277
/
Copy pathHttpClientDiscoveryExtensions.cs
147 lines (127 loc) · 5.81 KB
/
HttpClientDiscoveryExtensions.cs
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
// Copyright (c) Duende Software. All rights reserved.
// Licensed under the Apache License, Version 2.0. See LICENSE in the project root for license information.
using IdentityModel.Internal;
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
namespace IdentityModel.Client;
/// <summary>
/// HttpClient extentions for OIDC discovery
/// </summary>
public static class HttpClientDiscoveryExtensions
{
/// <summary>
/// Sends a discovery document request
/// </summary>
/// <param name="client">The client.</param>
/// <param name="address">The address.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public static async Task<DiscoveryDocumentResponse> GetDiscoveryDocumentAsync(this HttpClient client, string? address = null, CancellationToken cancellationToken = default)
{
if (address == null && client.BaseAddress == null)
throw new ArgumentException("Either the address parameter or the HttpClient BaseAddress must not be null.");
return await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest { Address = address }, cancellationToken).ConfigureAwait();
}
/// <summary>
/// Sends a discovery document request
/// </summary>
/// <param name="client">The client.</param>
/// <param name="request">The request.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns></returns>
public static async Task<DiscoveryDocumentResponse> GetDiscoveryDocumentAsync(this HttpMessageInvoker client, DiscoveryDocumentRequest request, CancellationToken cancellationToken = default)
{
string address;
if (request.Address.IsPresent())
{
address = request.Address!;
}
else if (client is HttpClient httpClient && httpClient.BaseAddress != null)
{
address = httpClient.BaseAddress!.AbsoluteUri;
}
else
{
throw new ArgumentException("Either the DiscoveryDocumentRequest Address or the HttpClient BaseAddress must not be null.");
}
var parsed = DiscoveryEndpoint.ParseUrl(address, request.Policy.DiscoveryDocumentPath);
var authority = parsed.Authority;
var url = parsed.Url;
if (request.Policy.Authority.IsMissing())
{
request.Policy.Authority = authority;
}
var jwkUrl = "";
if (!DiscoveryEndpoint.IsSecureScheme(new Uri(url), request.Policy))
{
return ProtocolResponse.FromException<DiscoveryDocumentResponse>(new InvalidOperationException("HTTPS required"), $"Error connecting to {url}. HTTPS required.");
}
try
{
var clone = request.Clone();
clone.Method = HttpMethod.Get;
clone.Prepare();
clone.RequestUri = new Uri(url);
var response = await client.SendAsync(clone, cancellationToken).ConfigureAwait();
if (!response.IsSuccessStatusCode)
{
return await ProtocolResponse.FromHttpResponseAsync<DiscoveryDocumentResponse>(response, $"Error connecting to {url}: {response.ReasonPhrase}").ConfigureAwait();
}
var disco = await ProtocolResponse.FromHttpResponseAsync<DiscoveryDocumentResponse>(response, request.Policy).ConfigureAwait();
if (disco.IsError)
{
return disco;
}
try
{
jwkUrl = disco.JwksUri;
if (jwkUrl != null)
{
var jwkClone = request.Clone<JsonWebKeySetRequest>();
jwkClone.Method = HttpMethod.Get;
jwkClone.Address = jwkUrl;
jwkClone.Prepare();
var jwkResponse = await client.GetJsonWebKeySetAsync(jwkClone, cancellationToken).ConfigureAwait();
if (jwkResponse.IsError)
{
if (jwkResponse.Exception != null)
{
return ProtocolResponse.FromException<DiscoveryDocumentResponse>(jwkResponse.Exception, jwkResponse.Error);
}
else if(jwkResponse.HttpResponse != null)
{
return await ProtocolResponse.FromHttpResponseAsync<DiscoveryDocumentResponse>(jwkResponse.HttpResponse, $"Error connecting to {jwkUrl}: {jwkResponse.HttpErrorReason}").ConfigureAwait();
}
// If IsError is true, but we have neither an Exception nor an HttpResponse, something very weird is going on
// I don't think it is actually possible for this to occur, but just in case...
else
{
return ProtocolResponse.FromException<DiscoveryDocumentResponse>(
new ArgumentNullException(nameof(jwkResponse.HttpResponse)), $"Unknown error retrieving JWKS - neither an exception nor an HttpResponse is available");
}
}
disco.KeySet = jwkResponse.KeySet;
}
return disco;
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
return ProtocolResponse.FromException<DiscoveryDocumentResponse>(ex, $"Error connecting to {jwkUrl}. {ex.Message}.");
}
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception ex)
{
return ProtocolResponse.FromException<DiscoveryDocumentResponse>(ex, $"Error connecting to {url}. {ex.Message}.");
}
}
}