Skip to content

Commit 94173b7

Browse files
committed
* Move ICredentialsProvider back to RabbitMQ.Client
* Use `ICredentialsProvider` in the auth mechanisms, async * Pass `CancellationToken` around and correctly dispose Http objects (thanks @Tornhoof) * Use `SemaphoreSlim`
1 parent a4a4da3 commit 94173b7

14 files changed

+94
-54
lines changed

projects/RabbitMQ.Client.OAuth2/OAuth2Client.cs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,15 @@
3434
using System.Net.Http;
3535
using System.Net.Http.Headers;
3636
using System.Net.Http.Json;
37+
using System.Threading;
3738
using System.Threading.Tasks;
3839

3940
namespace RabbitMQ.Client.OAuth2
4041
{
4142
public interface IOAuth2Client
4243
{
43-
Task<IToken> RequestTokenAsync();
44-
Task<IToken> RefreshTokenAsync(IToken token);
44+
Task<IToken> RequestTokenAsync(CancellationToken cancellationToken = default);
45+
Task<IToken> RefreshTokenAsync(IToken token, CancellationToken cancellationToken = default);
4546
}
4647

4748
public interface IToken
@@ -196,12 +197,12 @@ public OAuth2Client(string clientId, string clientSecret, Uri tokenEndpoint,
196197
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
197198
}
198199

199-
public async Task<IToken> RequestTokenAsync()
200+
public async Task<IToken> RequestTokenAsync(CancellationToken cancellationToken = default)
200201
{
201-
var req = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint);
202+
using HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint);
202203
req.Content = new FormUrlEncodedContent(buildRequestParameters());
203204

204-
HttpResponseMessage response = await _httpClient.SendAsync(req)
205+
using HttpResponseMessage response = await _httpClient.SendAsync(req)
205206
.ConfigureAwait(false);
206207

207208
response.EnsureSuccessStatusCode();
@@ -220,19 +221,20 @@ public async Task<IToken> RequestTokenAsync()
220221
}
221222
}
222223

223-
public async Task<IToken> RefreshTokenAsync(IToken token)
224+
public async Task<IToken> RefreshTokenAsync(IToken token,
225+
CancellationToken cancellationToken = default)
224226
{
225227
if (token.RefreshToken == null)
226228
{
227229
throw new InvalidOperationException("Token has no Refresh Token");
228230
}
229231

230-
var req = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint)
232+
using HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint)
231233
{
232234
Content = new FormUrlEncodedContent(buildRefreshParameters(token))
233235
};
234236

235-
HttpResponseMessage response = await _httpClient.SendAsync(req)
237+
using HttpResponseMessage response = await _httpClient.SendAsync(req)
236238
.ConfigureAwait(false);
237239

238240
response.EnsureSuccessStatusCode();

projects/RabbitMQ.Client.OAuth2/OAuth2CredentialsProvider.cs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,9 @@
3535

3636
namespace RabbitMQ.Client.OAuth2
3737
{
38-
public class OAuth2ClientCredentialsProvider : ICredentialsProvider
38+
public class OAuth2ClientCredentialsProvider : ICredentialsProvider, IDisposable
3939
{
40-
// TODO Dispose
41-
private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
40+
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
4241

4342
private readonly string _name;
4443
private readonly IOAuth2Client _oAuth2Client;
@@ -55,7 +54,8 @@ public OAuth2ClientCredentialsProvider(string name, IOAuth2Client oAuth2Client)
5554
public async Task<Credentials> GetCredentialsAsync(CancellationToken cancellationToken = default)
5655
{
5756
bool mustRetrieveToken = false;
58-
_lock.EnterReadLock();
57+
await _semaphore.WaitAsync(cancellationToken)
58+
.ConfigureAwait(false);
5959
try
6060
{
6161
if (_token is null || _token.HasExpired)
@@ -65,14 +65,14 @@ public async Task<Credentials> GetCredentialsAsync(CancellationToken cancellatio
6565
}
6666
finally
6767
{
68-
_lock.ExitReadLock();
68+
_semaphore.Release();
6969
}
7070

7171
cancellationToken.ThrowIfCancellationRequested();
7272

7373
if (mustRetrieveToken)
7474
{
75-
await RetrieveTokenAsync().ConfigureAwait(false);
75+
await RetrieveTokenAsync(cancellationToken).ConfigureAwait(false);
7676
}
7777

7878
if (_token is null)
@@ -86,25 +86,31 @@ public async Task<Credentials> GetCredentialsAsync(CancellationToken cancellatio
8686
}
8787
}
8888

89-
private async Task RetrieveTokenAsync()
89+
public void Dispose()
9090
{
91-
_lock.EnterWriteLock();
91+
_semaphore.Dispose();
92+
}
93+
94+
private async Task RetrieveTokenAsync(CancellationToken cancellationToken)
95+
{
96+
await _semaphore.WaitAsync(cancellationToken)
97+
.ConfigureAwait(false);
9298
try
9399
{
94100
if (_token == null || _token.RefreshToken == null)
95101
{
96-
_token = await _oAuth2Client.RequestTokenAsync()
102+
_token = await _oAuth2Client.RequestTokenAsync(cancellationToken)
97103
.ConfigureAwait(false);
98104
}
99105
else
100106
{
101-
_token = await _oAuth2Client.RefreshTokenAsync(_token)
107+
_token = await _oAuth2Client.RefreshTokenAsync(_token, cancellationToken)
102108
.ConfigureAwait(false);
103109
}
104110
}
105111
finally
106112
{
107-
_lock.ExitWriteLock();
113+
_semaphore.Release();
108114
}
109115
}
110116
}

projects/RabbitMQ.Client.OAuth2/PublicAPI.Unshipped.txt

Lines changed: 6 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
1-
RabbitMQ.Client.OAuth2.BasicCredentialsProvider
2-
RabbitMQ.Client.OAuth2.BasicCredentialsProvider.BasicCredentialsProvider(string! name, string! userName, string! password) -> void
3-
RabbitMQ.Client.OAuth2.BasicCredentialsProvider.GetCredentialsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<RabbitMQ.Client.OAuth2.Credentials!>!
4-
RabbitMQ.Client.OAuth2.BasicCredentialsProvider.Name.get -> string!
5-
RabbitMQ.Client.OAuth2.Credentials
6-
RabbitMQ.Client.OAuth2.Credentials.Credentials(string! name, string! userName, string! password, System.TimeSpan? validUntil) -> void
7-
RabbitMQ.Client.OAuth2.Credentials.Name.get -> string!
8-
RabbitMQ.Client.OAuth2.Credentials.Password.get -> string!
9-
RabbitMQ.Client.OAuth2.Credentials.UserName.get -> string!
10-
RabbitMQ.Client.OAuth2.Credentials.ValidUntil.get -> System.TimeSpan?
111
RabbitMQ.Client.OAuth2.CredentialsRefresher
12-
RabbitMQ.Client.OAuth2.CredentialsRefresher.CredentialsRefresher(RabbitMQ.Client.OAuth2.ICredentialsProvider! credentialsProvider, RabbitMQ.Client.OAuth2.NotifyCredentialsRefreshedAsync! onRefreshed, System.Threading.CancellationToken cancellationToken) -> void
2+
RabbitMQ.Client.OAuth2.CredentialsRefresher.Credentials.get -> RabbitMQ.Client.Credentials?
3+
RabbitMQ.Client.OAuth2.CredentialsRefresher.CredentialsRefresher(RabbitMQ.Client.ICredentialsProvider! credentialsProvider, RabbitMQ.Client.OAuth2.NotifyCredentialsRefreshedAsync! onRefreshed, System.Threading.CancellationToken cancellationToken) -> void
134
RabbitMQ.Client.OAuth2.CredentialsRefresher.Dispose() -> void
14-
RabbitMQ.Client.OAuth2.ICredentialsProvider
15-
RabbitMQ.Client.OAuth2.ICredentialsProvider.GetCredentialsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<RabbitMQ.Client.OAuth2.Credentials!>!
16-
RabbitMQ.Client.OAuth2.ICredentialsProvider.Name.get -> string!
17-
RabbitMQ.Client.OAuth2.IOAuth2Client.RefreshTokenAsync(RabbitMQ.Client.OAuth2.IToken! token) -> System.Threading.Tasks.Task<RabbitMQ.Client.OAuth2.IToken!>!
18-
RabbitMQ.Client.OAuth2.IOAuth2Client.RequestTokenAsync() -> System.Threading.Tasks.Task<RabbitMQ.Client.OAuth2.IToken!>!
5+
RabbitMQ.Client.OAuth2.IOAuth2Client.RefreshTokenAsync(RabbitMQ.Client.OAuth2.IToken! token, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<RabbitMQ.Client.OAuth2.IToken!>!
6+
RabbitMQ.Client.OAuth2.IOAuth2Client.RequestTokenAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<RabbitMQ.Client.OAuth2.IToken!>!
197
RabbitMQ.Client.OAuth2.NotifyCredentialsRefreshedAsync
20-
RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.GetCredentialsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<RabbitMQ.Client.OAuth2.Credentials!>!
8+
RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.Dispose() -> void
9+
RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.GetCredentialsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<RabbitMQ.Client.Credentials!>!
2110
RabbitMQ.Client.OAuth2.TimerBasedCredentialRefresherEventSource
2211
RabbitMQ.Client.OAuth2.TimerBasedCredentialRefresherEventSource.TimerBasedCredentialRefresherEventSource() -> void
2312
virtual RabbitMQ.Client.OAuth2.CredentialsRefresher.Dispose(bool disposing) -> void

projects/RabbitMQ.Client/PublicAPI.Shipped.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,6 @@ RabbitMQ.Client.Exceptions.WireFormattingException.WireFormattingException(strin
360360
RabbitMQ.Client.ExchangeType
361361
RabbitMQ.Client.ExternalMechanism
362362
RabbitMQ.Client.ExternalMechanism.ExternalMechanism() -> void
363-
RabbitMQ.Client.ExternalMechanism.handleChallenge(byte[] challenge, RabbitMQ.Client.ConnectionConfig config) -> byte[]
364363
RabbitMQ.Client.ExternalMechanismFactory
365364
RabbitMQ.Client.ExternalMechanismFactory.ExternalMechanismFactory() -> void
366365
RabbitMQ.Client.ExternalMechanismFactory.GetInstance() -> RabbitMQ.Client.IAuthMechanism
@@ -379,7 +378,6 @@ RabbitMQ.Client.IAsyncBasicConsumer.HandleBasicConsumeOkAsync(string consumerTag
379378
RabbitMQ.Client.IAsyncBasicConsumer.HandleBasicDeliverAsync(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, RabbitMQ.Client.IReadOnlyBasicProperties properties, System.ReadOnlyMemory<byte> body) -> System.Threading.Tasks.Task
380379
RabbitMQ.Client.IAsyncBasicConsumer.HandleChannelShutdownAsync(object channel, RabbitMQ.Client.ShutdownEventArgs reason) -> System.Threading.Tasks.Task
381380
RabbitMQ.Client.IAuthMechanism
382-
RabbitMQ.Client.IAuthMechanism.handleChallenge(byte[] challenge, RabbitMQ.Client.ConnectionConfig config) -> byte[]
383381
RabbitMQ.Client.IAuthMechanismFactory
384382
RabbitMQ.Client.IAuthMechanismFactory.GetInstance() -> RabbitMQ.Client.IAuthMechanism
385383
RabbitMQ.Client.IAuthMechanismFactory.Name.get -> string
@@ -584,7 +582,6 @@ RabbitMQ.Client.Logging.RabbitMqExceptionDetail.RabbitMqExceptionDetail(System.E
584582
RabbitMQ.Client.Logging.RabbitMqExceptionDetail.StackTrace.get -> string
585583
RabbitMQ.Client.Logging.RabbitMqExceptionDetail.Type.get -> string
586584
RabbitMQ.Client.PlainMechanism
587-
RabbitMQ.Client.PlainMechanism.handleChallenge(byte[] challenge, RabbitMQ.Client.ConnectionConfig config) -> byte[]
588585
RabbitMQ.Client.PlainMechanism.PlainMechanism() -> void
589586
RabbitMQ.Client.PlainMechanismFactory
590587
RabbitMQ.Client.PlainMechanismFactory.GetInstance() -> RabbitMQ.Client.IAuthMechanism
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,20 @@
1+
RabbitMQ.Client.BasicCredentialsProvider
2+
RabbitMQ.Client.BasicCredentialsProvider.BasicCredentialsProvider(string? name, string! userName, string! password) -> void
3+
RabbitMQ.Client.BasicCredentialsProvider.GetCredentialsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<RabbitMQ.Client.Credentials!>!
4+
RabbitMQ.Client.BasicCredentialsProvider.Name.get -> string!
15
RabbitMQ.Client.BasicProperties.BasicProperties(RabbitMQ.Client.IReadOnlyBasicProperties! input) -> void
6+
RabbitMQ.Client.ConnectionFactory.CredentialsProvider.get -> RabbitMQ.Client.ICredentialsProvider?
7+
RabbitMQ.Client.ConnectionFactory.CredentialsProvider.set -> void
8+
RabbitMQ.Client.Credentials
9+
RabbitMQ.Client.Credentials.Credentials(string! name, string! userName, string! password, System.TimeSpan? validUntil) -> void
10+
RabbitMQ.Client.Credentials.Name.get -> string!
11+
RabbitMQ.Client.Credentials.Password.get -> string!
12+
RabbitMQ.Client.Credentials.UserName.get -> string!
13+
RabbitMQ.Client.Credentials.ValidUntil.get -> System.TimeSpan?
14+
RabbitMQ.Client.ExternalMechanism.HandleChallengeAsync(byte[]? challenge, RabbitMQ.Client.ConnectionConfig! config, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<byte[]!>!
15+
RabbitMQ.Client.IAuthMechanism.HandleChallengeAsync(byte[]? challenge, RabbitMQ.Client.ConnectionConfig! config, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<byte[]!>!
16+
RabbitMQ.Client.ICredentialsProvider
17+
RabbitMQ.Client.ICredentialsProvider.GetCredentialsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<RabbitMQ.Client.Credentials!>!
18+
RabbitMQ.Client.ICredentialsProvider.Name.get -> string!
19+
RabbitMQ.Client.PlainMechanism.HandleChallengeAsync(byte[]? challenge, RabbitMQ.Client.ConnectionConfig! config, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task<byte[]!>!
20+
readonly RabbitMQ.Client.ConnectionConfig.CredentialsProvider -> RabbitMQ.Client.ICredentialsProvider!

projects/RabbitMQ.Client.OAuth2/BasicCredentialsProvider.cs renamed to projects/RabbitMQ.Client/client/api/BasicCredentialsProvider.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,28 @@
3333
using System.Threading;
3434
using System.Threading.Tasks;
3535

36-
namespace RabbitMQ.Client.OAuth2
36+
namespace RabbitMQ.Client
3737
{
3838
public class BasicCredentialsProvider : ICredentialsProvider
3939
{
4040
private readonly string _name;
4141
private readonly string _userName;
4242
private readonly string _password;
43+
private readonly Credentials _credentials;
4344

44-
public BasicCredentialsProvider(string name, string userName, string password)
45+
public BasicCredentialsProvider(string? name, string userName, string password)
4546
{
46-
_name = name ?? string.Empty;
47+
_name = name ?? typeof(BasicCredentialsProvider).Name;
4748
_userName = userName ?? throw new ArgumentNullException(nameof(userName));
4849
_password = password ?? throw new ArgumentNullException(nameof(password));
50+
_credentials = new Credentials(_name, _userName, _password, null);
4951
}
5052

5153
public string Name => _name;
5254

5355
public Task<Credentials> GetCredentialsAsync(CancellationToken cancellationToken = default)
5456
{
55-
return Task.FromResult(new Credentials(_name, _userName, _password, null));
57+
return Task.FromResult(_credentials);
5658
}
5759
}
5860
}

projects/RabbitMQ.Client/client/api/ConnectionConfig.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ public sealed class ConnectionConfig
5757
/// </summary>
5858
public readonly string Password;
5959

60+
/// <summary>
61+
/// Default ICredentialsProvider implementation. If set, this
62+
/// overrides UserName / Password
63+
/// </summary>
64+
public readonly ICredentialsProvider CredentialsProvider;
65+
6066
/// <summary>
6167
/// SASL auth mechanisms to use.
6268
/// </summary>
@@ -138,6 +144,7 @@ public sealed class ConnectionConfig
138144
internal readonly Func<AmqpTcpEndpoint, CancellationToken, Task<IFrameHandler>> FrameHandlerFactoryAsync;
139145

140146
internal ConnectionConfig(string virtualHost, string userName, string password,
147+
ICredentialsProvider? credentialsProvider,
141148
IEnumerable<IAuthMechanismFactory> authMechanisms,
142149
IDictionary<string, object?> clientProperties, string? clientProvidedName,
143150
ushort maxChannelCount, uint maxFrameSize, uint maxInboundMessageBodySize, bool topologyRecoveryEnabled,
@@ -148,6 +155,7 @@ internal ConnectionConfig(string virtualHost, string userName, string password,
148155
VirtualHost = virtualHost;
149156
UserName = userName;
150157
Password = password;
158+
CredentialsProvider = credentialsProvider ?? new BasicCredentialsProvider(clientProvidedName, userName, password);
151159
AuthMechanisms = authMechanisms;
152160
ClientProperties = clientProperties;
153161
ClientProvidedName = clientProvidedName;

projects/RabbitMQ.Client/client/api/ConnectionFactory.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,11 @@ public AmqpTcpEndpoint Endpoint
312312
/// </summary>
313313
public string Password { get; set; } = DefaultPass;
314314

315+
/// <summary>
316+
/// ICredentialsProvider used to obtain username and password.
317+
/// </summary>
318+
public ICredentialsProvider? CredentialsProvider { get; set; }
319+
315320
/// <summary>
316321
/// Maximum channel number to ask for.
317322
/// </summary>
@@ -578,6 +583,7 @@ private ConnectionConfig CreateConfig(string? clientProvidedName)
578583
VirtualHost,
579584
UserName,
580585
Password,
586+
CredentialsProvider,
581587
AuthMechanisms,
582588
ClientProperties,
583589
EnsureClientProvidedNameLength(clientProvidedName),

projects/RabbitMQ.Client/client/api/ExternalMechanism.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
//---------------------------------------------------------------------------
3131

3232
using System;
33+
using System.Threading;
34+
using System.Threading.Tasks;
3335

3436
namespace RabbitMQ.Client
3537
{
@@ -38,9 +40,10 @@ public class ExternalMechanism : IAuthMechanism
3840
/// <summary>
3941
/// Handle one round of challenge-response.
4042
/// </summary>
41-
public byte[] handleChallenge(byte[]? challenge, ConnectionConfig config)
43+
public Task<byte[]> HandleChallengeAsync(byte[]? challenge, ConnectionConfig config,
44+
CancellationToken cancellationToken = default)
4245
{
43-
return Array.Empty<byte>();
46+
return Task.FromResult(Array.Empty<byte>());
4447
}
4548
}
4649
}

projects/RabbitMQ.Client/client/api/IAuthMechanism.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
3030
//---------------------------------------------------------------------------
3131

32+
using System.Threading;
33+
using System.Threading.Tasks;
34+
3235
namespace RabbitMQ.Client
3336
{
3437
/// <summary>
@@ -39,6 +42,7 @@ public interface IAuthMechanism
3942
/// <summary>
4043
/// Handle one round of challenge-response.
4144
/// </summary>
42-
byte[] handleChallenge(byte[]? challenge, ConnectionConfig config);
45+
Task<byte[]> HandleChallengeAsync(byte[]? challenge, ConnectionConfig config,
46+
CancellationToken cancellationToken = default);
4347
}
4448
}

0 commit comments

Comments
 (0)