diff --git a/projects/Directory.Packages.props b/projects/Directory.Packages.props
index 649439c40e..34cca5f24a 100644
--- a/projects/Directory.Packages.props
+++ b/projects/Directory.Packages.props
@@ -5,21 +5,21 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
+
@@ -46,4 +46,4 @@
-
+
\ No newline at end of file
diff --git a/projects/RabbitMQ.Client.OAuth2/CredentialsRefresher.cs b/projects/RabbitMQ.Client.OAuth2/CredentialsRefresher.cs
new file mode 100644
index 0000000000..14b639b712
--- /dev/null
+++ b/projects/RabbitMQ.Client.OAuth2/CredentialsRefresher.cs
@@ -0,0 +1,144 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace RabbitMQ.Client.OAuth2
+{
+ public delegate Task NotifyCredentialsRefreshedAsync(Credentials? credentials,
+ Exception? exception = null,
+ CancellationToken cancellationToken = default);
+
+ public class CredentialsRefresher : IDisposable
+ {
+ private readonly ICredentialsProvider _credentialsProvider;
+ private readonly NotifyCredentialsRefreshedAsync _onRefreshed;
+
+ private readonly CancellationTokenSource _internalCts = new CancellationTokenSource();
+ private readonly CancellationTokenSource _linkedCts;
+
+ private readonly Task _refreshTask;
+
+ private Credentials? _credentials;
+ private bool _disposedValue = false;
+
+ public CredentialsRefresher(ICredentialsProvider credentialsProvider,
+ NotifyCredentialsRefreshedAsync onRefreshed,
+ CancellationToken cancellationToken)
+ {
+ if (credentialsProvider is null)
+ {
+ throw new ArgumentNullException(nameof(credentialsProvider));
+ }
+
+ if (onRefreshed is null)
+ {
+ throw new ArgumentNullException(nameof(onRefreshed));
+ }
+
+ _linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_internalCts.Token, cancellationToken);
+
+ _credentialsProvider = credentialsProvider;
+ _onRefreshed = onRefreshed;
+
+ _refreshTask = Task.Run(RefreshLoopAsync, _linkedCts.Token);
+
+ CredentialsRefresherEventSource.Log.Started(_credentialsProvider.Name);
+ }
+
+ public Credentials? Credentials => _credentials;
+
+ private async Task RefreshLoopAsync()
+ {
+ while (false == _linkedCts.IsCancellationRequested)
+ {
+ try
+ {
+ _credentials = await _credentialsProvider.GetCredentialsAsync(_linkedCts.Token)
+ .ConfigureAwait(false);
+
+ if (_linkedCts.IsCancellationRequested)
+ {
+ break;
+ }
+
+ CredentialsRefresherEventSource.Log.RefreshedCredentials(_credentialsProvider.Name);
+
+ await _onRefreshed(_credentials, null, _linkedCts.Token)
+ .ConfigureAwait(false);
+ }
+ catch (OperationCanceledException)
+ {
+ return;
+ }
+ catch (Exception ex)
+ {
+ await _onRefreshed(null, ex, _linkedCts.Token)
+ .ConfigureAwait(false);
+ }
+
+ TimeSpan delaySpan = TimeSpan.FromSeconds(30);
+ if (_credentials != null && _credentials.ValidUntil.HasValue)
+ {
+ delaySpan = TimeSpan.FromMilliseconds(_credentials.ValidUntil.Value.TotalMilliseconds * (1.0 - (1 / 3.0)));
+ }
+
+ await Task.Delay(delaySpan, _linkedCts.Token)
+ .ConfigureAwait(false);
+ }
+ }
+
+ public void Dispose()
+ {
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ _internalCts.Cancel();
+ _refreshTask.Wait(TimeSpan.FromSeconds(5));
+ _internalCts.Dispose();
+ _linkedCts.Dispose();
+ CredentialsRefresherEventSource.Log.Stopped(_credentialsProvider.Name);
+ }
+
+ _disposedValue = true;
+ }
+ }
+ }
+}
diff --git a/projects/RabbitMQ.Client.OAuth2/CredentialsRefresherEventSource.cs b/projects/RabbitMQ.Client.OAuth2/CredentialsRefresherEventSource.cs
new file mode 100644
index 0000000000..b9d6707941
--- /dev/null
+++ b/projects/RabbitMQ.Client.OAuth2/CredentialsRefresherEventSource.cs
@@ -0,0 +1,54 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//---------------------------------------------------------------------------
+
+using System.Diagnostics.CodeAnalysis;
+using System.Diagnostics.Tracing;
+
+namespace RabbitMQ.Client.OAuth2
+{
+ [EventSource(Name = "CredentialRefresher")]
+ public class CredentialsRefresherEventSource : EventSource
+ {
+ public static CredentialsRefresherEventSource Log { get; } = new CredentialsRefresherEventSource();
+
+ [Event(1)]
+ public void Started(string name) => WriteEvent(1, "Started", name);
+
+ [Event(2)]
+ public void Stopped(string name) => WriteEvent(2, "Stopped", name);
+
+ [Event(3)]
+#if NET6_0_OR_GREATER
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe")]
+#endif
+ public void RefreshedCredentials(string name) => WriteEvent(3, "RefreshedCredentials", name);
+ }
+}
diff --git a/projects/RabbitMQ.Client.OAuth2/IOAuth2Client.cs b/projects/RabbitMQ.Client.OAuth2/IOAuth2Client.cs
new file mode 100644
index 0000000000..ee1f025a92
--- /dev/null
+++ b/projects/RabbitMQ.Client.OAuth2/IOAuth2Client.cs
@@ -0,0 +1,42 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//---------------------------------------------------------------------------
+
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace RabbitMQ.Client.OAuth2
+{
+ public interface IOAuth2Client
+ {
+ Task RequestTokenAsync(CancellationToken cancellationToken = default);
+ Task RefreshTokenAsync(IToken token, CancellationToken cancellationToken = default);
+ }
+}
diff --git a/projects/RabbitMQ.Client.OAuth2/OAuth2Client.cs b/projects/RabbitMQ.Client.OAuth2/OAuth2Client.cs
index 2598d3a4e7..d14c0f725e 100644
--- a/projects/RabbitMQ.Client.OAuth2/OAuth2Client.cs
+++ b/projects/RabbitMQ.Client.OAuth2/OAuth2Client.cs
@@ -34,84 +34,26 @@
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
+using System.Text.Json.Serialization;
+using System.Threading;
using System.Threading.Tasks;
namespace RabbitMQ.Client.OAuth2
{
- public interface IOAuth2Client
- {
- IToken RequestToken();
- IToken RefreshToken(IToken token);
- }
-
- public interface IToken
- {
- string AccessToken { get; }
- string RefreshToken { get; }
- TimeSpan ExpiresIn { get; }
- bool hasExpired { get; }
- }
-
- public class Token : IToken
- {
- private readonly JsonToken _source;
- private readonly DateTime _lastTokenRenewal;
-
- public Token(JsonToken json)
- {
- this._source = json;
- this._lastTokenRenewal = DateTime.Now;
- }
-
- public string AccessToken
- {
- get
- {
- return _source.access_token;
- }
- }
-
- public string RefreshToken
- {
- get
- {
- return _source.refresh_token;
- }
- }
-
- public TimeSpan ExpiresIn
- {
- get
- {
- return TimeSpan.FromSeconds(_source.expires_in);
- }
- }
-
- bool IToken.hasExpired
- {
- get
- {
- TimeSpan age = DateTime.Now - _lastTokenRenewal;
- return age > ExpiresIn;
- }
- }
- }
-
public class OAuth2ClientBuilder
{
private readonly string _clientId;
private readonly string _clientSecret;
private readonly Uri _tokenEndpoint;
- private string _scope;
- private IDictionary _additionalRequestParameters;
- private HttpClientHandler _httpClientHandler;
+ private string? _scope;
+ private IDictionary? _additionalRequestParameters;
+ private HttpClientHandler? _httpClientHandler;
public OAuth2ClientBuilder(string clientId, string clientSecret, Uri tokenEndpoint)
{
_clientId = clientId ?? throw new ArgumentNullException(nameof(clientId));
_clientSecret = clientSecret ?? throw new ArgumentNullException(nameof(clientSecret));
_tokenEndpoint = tokenEndpoint ?? throw new ArgumentNullException(nameof(tokenEndpoint));
-
}
public OAuth2ClientBuilder SetScope(string scope)
@@ -132,15 +74,18 @@ public OAuth2ClientBuilder AddRequestParameter(string param, string paramValue)
{
throw new ArgumentNullException("param is null");
}
+
if (paramValue == null)
{
throw new ArgumentNullException("paramValue is null");
}
+
if (_additionalRequestParameters == null)
{
_additionalRequestParameters = new Dictionary();
}
_additionalRequestParameters[param] = paramValue;
+
return this;
}
@@ -169,60 +114,91 @@ internal class OAuth2Client : IOAuth2Client, IDisposable
private readonly string _clientId;
private readonly string _clientSecret;
private readonly Uri _tokenEndpoint;
- private readonly string _scope;
+ private readonly string? _scope;
private readonly IDictionary _additionalRequestParameters;
public static readonly IDictionary EMPTY = new Dictionary();
private HttpClient _httpClient;
- public OAuth2Client(string clientId, string clientSecret, Uri tokenEndpoint, string scope,
- IDictionary additionalRequestParameters,
- HttpClientHandler httpClientHandler)
+ public OAuth2Client(string clientId, string clientSecret, Uri tokenEndpoint,
+ string? scope,
+ IDictionary? additionalRequestParameters,
+ HttpClientHandler? httpClientHandler)
{
- this._clientId = clientId;
- this._clientSecret = clientSecret;
- this._scope = scope;
- this._additionalRequestParameters = additionalRequestParameters == null ? EMPTY : additionalRequestParameters;
- this._tokenEndpoint = tokenEndpoint;
-
- _httpClient = httpClientHandler == null ? new HttpClient() :
- new HttpClient(httpClientHandler);
+ _clientId = clientId;
+ _clientSecret = clientSecret;
+ _scope = scope;
+ _additionalRequestParameters = additionalRequestParameters ?? EMPTY;
+ _tokenEndpoint = tokenEndpoint;
+
+ if (httpClientHandler is null)
+ {
+ _httpClient = new HttpClient();
+ }
+ else
+ {
+ _httpClient = new HttpClient(httpClientHandler, false);
+ }
+
_httpClient.DefaultRequestHeaders.Accept.Clear();
_httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}
- public IToken RequestToken()
+ public async Task RequestTokenAsync(CancellationToken cancellationToken = default)
{
- var req = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint);
- req.Content = new FormUrlEncodedContent(buildRequestParameters());
-
- Task response = _httpClient.SendAsync(req);
- response.Wait();
- response.Result.EnsureSuccessStatusCode();
- Task token = response.Result.Content.ReadFromJsonAsync();
- token.Wait();
- return new Token(token.Result);
+ using HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint);
+ req.Content = new FormUrlEncodedContent(BuildRequestParameters());
+
+ using HttpResponseMessage response = await _httpClient.SendAsync(req)
+ .ConfigureAwait(false);
+
+ response.EnsureSuccessStatusCode();
+
+ JsonToken? token = await response.Content.ReadFromJsonAsync()
+ .ConfigureAwait(false);
+
+ if (token is null)
+ {
+ // TODO specific exception?
+ throw new InvalidOperationException("token is null");
+ }
+ else
+ {
+ return new Token(token);
+ }
}
- public IToken RefreshToken(IToken token)
+ public async Task RefreshTokenAsync(IToken token,
+ CancellationToken cancellationToken = default)
{
if (token.RefreshToken == null)
{
throw new InvalidOperationException("Token has no Refresh Token");
}
- var req = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint)
+ using HttpRequestMessage req = new HttpRequestMessage(HttpMethod.Post, _tokenEndpoint)
{
- Content = new FormUrlEncodedContent(buildRefreshParameters(token))
+ Content = new FormUrlEncodedContent(BuildRefreshParameters(token))
};
- Task response = _httpClient.SendAsync(req);
- response.Wait();
- response.Result.EnsureSuccessStatusCode();
- Task refreshedToken = response.Result.Content.ReadFromJsonAsync();
- refreshedToken.Wait();
- return new Token(refreshedToken.Result);
+ using HttpResponseMessage response = await _httpClient.SendAsync(req)
+ .ConfigureAwait(false);
+
+ response.EnsureSuccessStatusCode();
+
+ JsonToken? refreshedToken = await response.Content.ReadFromJsonAsync()
+ .ConfigureAwait(false);
+
+ if (refreshedToken is null)
+ {
+ // TODO specific exception?
+ throw new InvalidOperationException("refreshed token is null");
+ }
+ else
+ {
+ return new Token(refreshedToken);
+ }
}
public void Dispose()
@@ -230,66 +206,80 @@ public void Dispose()
_httpClient.Dispose();
}
- private Dictionary buildRequestParameters()
+ private Dictionary BuildRequestParameters()
{
var dict = new Dictionary(_additionalRequestParameters)
{
{ CLIENT_ID, _clientId },
{ CLIENT_SECRET, _clientSecret }
};
+
if (_scope != null && _scope.Length > 0)
{
dict.Add(SCOPE, _scope);
}
+
dict.Add(GRANT_TYPE, GRANT_TYPE_CLIENT_CREDENTIALS);
+
return dict;
}
- private Dictionary buildRefreshParameters(IToken token)
+ private Dictionary BuildRefreshParameters(IToken token)
{
- var dict = buildRequestParameters();
+ Dictionary dict = BuildRequestParameters();
dict.Remove(GRANT_TYPE);
dict.Add(GRANT_TYPE, REFRESH_TOKEN);
+
if (_scope != null)
{
dict.Add(SCOPE, _scope);
}
- dict.Add(REFRESH_TOKEN, token.RefreshToken);
+
+ if (token.RefreshToken != null)
+ {
+ dict.Add(REFRESH_TOKEN, token.RefreshToken);
+ }
+
return dict;
}
}
- public class JsonToken
+ internal class JsonToken
{
public JsonToken()
{
+ AccessToken = string.Empty;
+ RefreshToken = string.Empty;
}
public JsonToken(string access_token, string refresh_token, TimeSpan expires_in_span)
{
- this.access_token = access_token;
- this.refresh_token = refresh_token;
- this.expires_in = (long)expires_in_span.TotalSeconds;
+ AccessToken = access_token;
+ RefreshToken = refresh_token;
+ ExpiresIn = (long)expires_in_span.TotalSeconds;
}
public JsonToken(string access_token, string refresh_token, long expires_in)
{
- this.access_token = access_token;
- this.refresh_token = refresh_token;
- this.expires_in = expires_in;
+ AccessToken = access_token;
+ RefreshToken = refresh_token;
+ ExpiresIn = expires_in;
}
- public string access_token
+ [JsonPropertyName("access_token")]
+ public string AccessToken
{
get; set;
}
- public string refresh_token
+ [JsonPropertyName("refresh_token")]
+ public string? RefreshToken
{
get; set;
}
- public long expires_in
+ [JsonPropertyName("expires_in")]
+ public long ExpiresIn
{
get; set;
}
diff --git a/projects/RabbitMQ.Client.OAuth2/OAuth2CredentialsProvider.cs b/projects/RabbitMQ.Client.OAuth2/OAuth2CredentialsProvider.cs
index f59b577a9b..3033a2a954 100644
--- a/projects/RabbitMQ.Client.OAuth2/OAuth2CredentialsProvider.cs
+++ b/projects/RabbitMQ.Client.OAuth2/OAuth2CredentialsProvider.cs
@@ -31,17 +31,17 @@
using System;
using System.Threading;
+using System.Threading.Tasks;
namespace RabbitMQ.Client.OAuth2
{
- public class OAuth2ClientCredentialsProvider : ICredentialsProvider
+ public class OAuth2ClientCredentialsProvider : ICredentialsProvider, IDisposable
{
- const int TOKEN_RETRIEVAL_TIMEOUT = 5000;
- private ReaderWriterLock _lock = new ReaderWriterLock();
+ private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
private readonly string _name;
private readonly IOAuth2Client _oAuth2Client;
- private IToken _token;
+ private IToken? _token;
public OAuth2ClientCredentialsProvider(string name, IOAuth2Client oAuth2Client)
{
@@ -49,94 +49,44 @@ public OAuth2ClientCredentialsProvider(string name, IOAuth2Client oAuth2Client)
_oAuth2Client = oAuth2Client ?? throw new ArgumentNullException(nameof(oAuth2Client));
}
- public string Name
- {
- get
- {
- return _name;
- }
- }
-
- public string UserName
- {
- get
- {
- checkState();
- return string.Empty;
- }
- }
+ public string Name => _name;
- public string Password
+ public async Task GetCredentialsAsync(CancellationToken cancellationToken = default)
{
- get
- {
- return checkState().AccessToken;
- }
- }
-
- public Nullable ValidUntil
- {
- get
+ await _semaphore.WaitAsync(cancellationToken)
+ .ConfigureAwait(false);
+ try
{
- IToken t = checkState();
- if (t is null)
+ if (_token == null || string.IsNullOrEmpty(_token.RefreshToken))
{
- return null;
+ _token = await _oAuth2Client.RequestTokenAsync(cancellationToken)
+ .ConfigureAwait(false);
}
else
{
- return t.ExpiresIn;
- }
- }
- }
-
- public void Refresh()
- {
- retrieveToken();
- }
-
- private IToken checkState()
- {
- _lock.AcquireReaderLock(TOKEN_RETRIEVAL_TIMEOUT);
- try
- {
- if (_token != null)
- {
- return _token;
+ _token = await _oAuth2Client.RefreshTokenAsync(_token, cancellationToken)
+ .ConfigureAwait(false);
}
}
finally
{
- _lock.ReleaseReaderLock();
+ _semaphore.Release();
}
- return retrieveToken();
- }
-
- private IToken retrieveToken()
- {
- _lock.AcquireWriterLock(TOKEN_RETRIEVAL_TIMEOUT);
- try
+ if (_token is null)
{
- return requestOrRenewToken();
+ throw new InvalidOperationException("_token should not be null here");
}
- finally
+ else
{
- _lock.ReleaseReaderLock();
+ return new Credentials(_name, string.Empty,
+ _token.AccessToken, _token.ExpiresIn);
}
}
- private IToken requestOrRenewToken()
+ public void Dispose()
{
- if (_token == null || _token.RefreshToken == null)
- {
- _token = _oAuth2Client.RequestToken();
- }
- else
- {
- _token = _oAuth2Client.RefreshToken(_token);
- }
- return _token;
+ _semaphore.Dispose();
}
}
}
diff --git a/projects/RabbitMQ.Client.OAuth2/PublicAPI.Shipped.txt b/projects/RabbitMQ.Client.OAuth2/PublicAPI.Shipped.txt
index e6e770d5bd..ddd490940c 100644
--- a/projects/RabbitMQ.Client.OAuth2/PublicAPI.Shipped.txt
+++ b/projects/RabbitMQ.Client.OAuth2/PublicAPI.Shipped.txt
@@ -1,37 +1,19 @@
#nullable enable
RabbitMQ.Client.OAuth2.IOAuth2Client
-RabbitMQ.Client.OAuth2.IOAuth2Client.RefreshToken(RabbitMQ.Client.OAuth2.IToken token) -> RabbitMQ.Client.OAuth2.IToken
-RabbitMQ.Client.OAuth2.IOAuth2Client.RequestToken() -> RabbitMQ.Client.OAuth2.IToken
RabbitMQ.Client.OAuth2.IToken
RabbitMQ.Client.OAuth2.IToken.AccessToken.get -> string
RabbitMQ.Client.OAuth2.IToken.ExpiresIn.get -> System.TimeSpan
-RabbitMQ.Client.OAuth2.IToken.hasExpired.get -> bool
+RabbitMQ.Client.OAuth2.IToken.HasExpired.get -> bool
RabbitMQ.Client.OAuth2.IToken.RefreshToken.get -> string
-RabbitMQ.Client.OAuth2.JsonToken
-RabbitMQ.Client.OAuth2.JsonToken.access_token.get -> string
-RabbitMQ.Client.OAuth2.JsonToken.access_token.set -> void
-RabbitMQ.Client.OAuth2.JsonToken.expires_in.get -> long
-RabbitMQ.Client.OAuth2.JsonToken.expires_in.set -> void
-RabbitMQ.Client.OAuth2.JsonToken.JsonToken() -> void
-RabbitMQ.Client.OAuth2.JsonToken.JsonToken(string access_token, string refresh_token, long expires_in) -> void
-RabbitMQ.Client.OAuth2.JsonToken.JsonToken(string access_token, string refresh_token, System.TimeSpan expires_in_span) -> void
-RabbitMQ.Client.OAuth2.JsonToken.refresh_token.get -> string
-RabbitMQ.Client.OAuth2.JsonToken.refresh_token.set -> void
RabbitMQ.Client.OAuth2.OAuth2ClientBuilder
RabbitMQ.Client.OAuth2.OAuth2ClientBuilder.AddRequestParameter(string param, string paramValue) -> RabbitMQ.Client.OAuth2.OAuth2ClientBuilder
RabbitMQ.Client.OAuth2.OAuth2ClientBuilder.Build() -> RabbitMQ.Client.OAuth2.IOAuth2Client
RabbitMQ.Client.OAuth2.OAuth2ClientBuilder.OAuth2ClientBuilder(string clientId, string clientSecret, System.Uri tokenEndpoint) -> void
-RabbitMQ.Client.OAuth2.OAuth2ClientBuilder.SetHttpClientHandler(System.Net.Http.HttpClientHandler handler) -> RabbitMQ.Client.OAuth2.OAuth2ClientBuilder
RabbitMQ.Client.OAuth2.OAuth2ClientBuilder.SetScope(string scope) -> RabbitMQ.Client.OAuth2.OAuth2ClientBuilder
RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider
RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.Name.get -> string
RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.OAuth2ClientCredentialsProvider(string name, RabbitMQ.Client.OAuth2.IOAuth2Client oAuth2Client) -> void
-RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.Password.get -> string
-RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.Refresh() -> void
-RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.UserName.get -> string
-RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.ValidUntil.get -> System.TimeSpan?
RabbitMQ.Client.OAuth2.Token
RabbitMQ.Client.OAuth2.Token.AccessToken.get -> string
RabbitMQ.Client.OAuth2.Token.ExpiresIn.get -> System.TimeSpan
RabbitMQ.Client.OAuth2.Token.RefreshToken.get -> string
-RabbitMQ.Client.OAuth2.Token.Token(RabbitMQ.Client.OAuth2.JsonToken json) -> void
diff --git a/projects/RabbitMQ.Client.OAuth2/PublicAPI.Unshipped.txt b/projects/RabbitMQ.Client.OAuth2/PublicAPI.Unshipped.txt
index e69de29bb2..f93749f14d 100644
--- a/projects/RabbitMQ.Client.OAuth2/PublicAPI.Unshipped.txt
+++ b/projects/RabbitMQ.Client.OAuth2/PublicAPI.Unshipped.txt
@@ -0,0 +1,17 @@
+RabbitMQ.Client.OAuth2.CredentialsRefresher
+RabbitMQ.Client.OAuth2.CredentialsRefresher.Credentials.get -> RabbitMQ.Client.Credentials?
+RabbitMQ.Client.OAuth2.CredentialsRefresher.CredentialsRefresher(RabbitMQ.Client.ICredentialsProvider! credentialsProvider, RabbitMQ.Client.OAuth2.NotifyCredentialsRefreshedAsync! onRefreshed, System.Threading.CancellationToken cancellationToken) -> void
+RabbitMQ.Client.OAuth2.CredentialsRefresher.Dispose() -> void
+RabbitMQ.Client.OAuth2.CredentialsRefresherEventSource
+RabbitMQ.Client.OAuth2.CredentialsRefresherEventSource.CredentialsRefresherEventSource() -> void
+RabbitMQ.Client.OAuth2.CredentialsRefresherEventSource.RefreshedCredentials(string! name) -> void
+RabbitMQ.Client.OAuth2.CredentialsRefresherEventSource.Started(string! name) -> void
+RabbitMQ.Client.OAuth2.CredentialsRefresherEventSource.Stopped(string! name) -> void
+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.IOAuth2Client.RequestTokenAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+RabbitMQ.Client.OAuth2.NotifyCredentialsRefreshedAsync
+RabbitMQ.Client.OAuth2.OAuth2ClientBuilder.SetHttpClientHandler(System.Net.Http.HttpClientHandler! handler) -> RabbitMQ.Client.OAuth2.OAuth2ClientBuilder!
+RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.Dispose() -> void
+RabbitMQ.Client.OAuth2.OAuth2ClientCredentialsProvider.GetCredentialsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+static RabbitMQ.Client.OAuth2.CredentialsRefresherEventSource.Log.get -> RabbitMQ.Client.OAuth2.CredentialsRefresherEventSource!
+virtual RabbitMQ.Client.OAuth2.CredentialsRefresher.Dispose(bool disposing) -> void
diff --git a/projects/RabbitMQ.Client.OAuth2/RabbitMQ.Client.OAuth2.csproj b/projects/RabbitMQ.Client.OAuth2/RabbitMQ.Client.OAuth2.csproj
index 30f8a1db87..33a6a67ff0 100644
--- a/projects/RabbitMQ.Client.OAuth2/RabbitMQ.Client.OAuth2.csproj
+++ b/projects/RabbitMQ.Client.OAuth2/RabbitMQ.Client.OAuth2.csproj
@@ -1,4 +1,4 @@
-
+
net6.0;netstandard2.0
@@ -27,7 +27,12 @@
true
../../packages
README.md
- 7.3
+
+ 8.0
+ enable
@@ -44,8 +49,8 @@
-
+
diff --git a/projects/RabbitMQ.Client.OAuth2/Token.cs b/projects/RabbitMQ.Client.OAuth2/Token.cs
new file mode 100644
index 0000000000..4c7e4941d2
--- /dev/null
+++ b/projects/RabbitMQ.Client.OAuth2/Token.cs
@@ -0,0 +1,88 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//---------------------------------------------------------------------------
+
+using System;
+
+namespace RabbitMQ.Client.OAuth2
+{
+ public interface IToken
+ {
+ string AccessToken { get; }
+ string? RefreshToken { get; }
+ TimeSpan ExpiresIn { get; }
+ bool HasExpired { get; }
+ }
+
+ public class Token : IToken
+ {
+ private readonly JsonToken _source;
+ private readonly DateTime _lastTokenRenewal;
+
+ internal Token(JsonToken json)
+ {
+ _source = json;
+ _lastTokenRenewal = DateTime.Now;
+ }
+
+ public string AccessToken
+ {
+ get
+ {
+ return _source.AccessToken;
+ }
+ }
+
+ public string? RefreshToken
+ {
+ get
+ {
+ return _source.RefreshToken;
+ }
+ }
+
+ public TimeSpan ExpiresIn
+ {
+ get
+ {
+ return TimeSpan.FromSeconds(_source.ExpiresIn);
+ }
+ }
+
+ bool IToken.HasExpired
+ {
+ get
+ {
+ TimeSpan age = DateTime.Now - _lastTokenRenewal;
+ return age > ExpiresIn;
+ }
+ }
+ }
+}
diff --git a/projects/RabbitMQ.Client/PublicAPI.Shipped.txt b/projects/RabbitMQ.Client/PublicAPI.Shipped.txt
index 7283f7a413..d2b1c0882a 100644
--- a/projects/RabbitMQ.Client/PublicAPI.Shipped.txt
+++ b/projects/RabbitMQ.Client/PublicAPI.Shipped.txt
@@ -105,13 +105,6 @@ RabbitMQ.Client.AsyncDefaultBasicConsumer.IsRunning.get -> bool
RabbitMQ.Client.AsyncDefaultBasicConsumer.IsRunning.set -> void
RabbitMQ.Client.AsyncDefaultBasicConsumer.ShutdownReason.get -> RabbitMQ.Client.ShutdownEventArgs
RabbitMQ.Client.AsyncDefaultBasicConsumer.ShutdownReason.set -> void
-RabbitMQ.Client.BasicCredentialsProvider
-RabbitMQ.Client.BasicCredentialsProvider.BasicCredentialsProvider(string name, string userName, string password) -> void
-RabbitMQ.Client.BasicCredentialsProvider.Name.get -> string
-RabbitMQ.Client.BasicCredentialsProvider.Password.get -> string
-RabbitMQ.Client.BasicCredentialsProvider.Refresh() -> void
-RabbitMQ.Client.BasicCredentialsProvider.UserName.get -> string
-RabbitMQ.Client.BasicCredentialsProvider.ValidUntil.get -> System.TimeSpan?
RabbitMQ.Client.BasicGetResult
RabbitMQ.Client.BasicGetResult.BasicGetResult(ulong deliveryTag, bool redelivered, string exchange, string routingKey, uint messageCount, RabbitMQ.Client.IReadOnlyBasicProperties basicProperties, System.ReadOnlyMemory body) -> void
RabbitMQ.Client.BasicProperties
@@ -187,8 +180,6 @@ RabbitMQ.Client.CachedString.CachedString(string value) -> void
RabbitMQ.Client.CachedString.CachedString(string value, System.ReadOnlyMemory bytes) -> void
RabbitMQ.Client.CachedString.CachedString(System.ReadOnlyMemory bytes) -> void
RabbitMQ.Client.ConnectionConfig
-RabbitMQ.Client.ConnectionConfig.CredentialsProvider -> RabbitMQ.Client.ICredentialsProvider
-RabbitMQ.Client.ConnectionConfig.CredentialsRefresher -> RabbitMQ.Client.ICredentialsRefresher
RabbitMQ.Client.ConnectionFactory
RabbitMQ.Client.ConnectionFactory.AmqpUriSslProtocols.get -> System.Security.Authentication.SslProtocols
RabbitMQ.Client.ConnectionFactory.AmqpUriSslProtocols.set -> void
@@ -206,10 +197,6 @@ RabbitMQ.Client.ConnectionFactory.ConsumerDispatchConcurrency.get -> int
RabbitMQ.Client.ConnectionFactory.ConsumerDispatchConcurrency.set -> void
RabbitMQ.Client.ConnectionFactory.ContinuationTimeout.get -> System.TimeSpan
RabbitMQ.Client.ConnectionFactory.ContinuationTimeout.set -> void
-RabbitMQ.Client.ConnectionFactory.CredentialsProvider.get -> RabbitMQ.Client.ICredentialsProvider
-RabbitMQ.Client.ConnectionFactory.CredentialsProvider.set -> void
-RabbitMQ.Client.ConnectionFactory.CredentialsRefresher.get -> RabbitMQ.Client.ICredentialsRefresher
-RabbitMQ.Client.ConnectionFactory.CredentialsRefresher.set -> void
RabbitMQ.Client.ConnectionFactory.Endpoint.get -> RabbitMQ.Client.AmqpTcpEndpoint
RabbitMQ.Client.ConnectionFactory.Endpoint.set -> void
RabbitMQ.Client.ConnectionFactory.EndpointResolverFactory.get -> System.Func, RabbitMQ.Client.IEndpointResolver>
@@ -373,7 +360,6 @@ RabbitMQ.Client.Exceptions.WireFormattingException.WireFormattingException(strin
RabbitMQ.Client.ExchangeType
RabbitMQ.Client.ExternalMechanism
RabbitMQ.Client.ExternalMechanism.ExternalMechanism() -> void
-RabbitMQ.Client.ExternalMechanism.handleChallenge(byte[] challenge, RabbitMQ.Client.ConnectionConfig config) -> byte[]
RabbitMQ.Client.ExternalMechanismFactory
RabbitMQ.Client.ExternalMechanismFactory.ExternalMechanismFactory() -> void
RabbitMQ.Client.ExternalMechanismFactory.GetInstance() -> RabbitMQ.Client.IAuthMechanism
@@ -392,7 +378,6 @@ RabbitMQ.Client.IAsyncBasicConsumer.HandleBasicConsumeOkAsync(string consumerTag
RabbitMQ.Client.IAsyncBasicConsumer.HandleBasicDeliverAsync(string consumerTag, ulong deliveryTag, bool redelivered, string exchange, string routingKey, RabbitMQ.Client.IReadOnlyBasicProperties properties, System.ReadOnlyMemory body) -> System.Threading.Tasks.Task
RabbitMQ.Client.IAsyncBasicConsumer.HandleChannelShutdownAsync(object channel, RabbitMQ.Client.ShutdownEventArgs reason) -> System.Threading.Tasks.Task
RabbitMQ.Client.IAuthMechanism
-RabbitMQ.Client.IAuthMechanism.handleChallenge(byte[] challenge, RabbitMQ.Client.ConnectionConfig config) -> byte[]
RabbitMQ.Client.IAuthMechanismFactory
RabbitMQ.Client.IAuthMechanismFactory.GetInstance() -> RabbitMQ.Client.IAuthMechanism
RabbitMQ.Client.IAuthMechanismFactory.Name.get -> string
@@ -497,10 +482,6 @@ RabbitMQ.Client.IConnectionFactory.ConsumerDispatchConcurrency.get -> int
RabbitMQ.Client.IConnectionFactory.ConsumerDispatchConcurrency.set -> void
RabbitMQ.Client.IConnectionFactory.ContinuationTimeout.get -> System.TimeSpan
RabbitMQ.Client.IConnectionFactory.ContinuationTimeout.set -> void
-RabbitMQ.Client.IConnectionFactory.CredentialsProvider.get -> RabbitMQ.Client.ICredentialsProvider
-RabbitMQ.Client.IConnectionFactory.CredentialsProvider.set -> void
-RabbitMQ.Client.IConnectionFactory.CredentialsRefresher.get -> RabbitMQ.Client.ICredentialsRefresher
-RabbitMQ.Client.IConnectionFactory.CredentialsRefresher.set -> void
RabbitMQ.Client.IConnectionFactory.HandshakeContinuationTimeout.get -> System.TimeSpan
RabbitMQ.Client.IConnectionFactory.HandshakeContinuationTimeout.set -> void
RabbitMQ.Client.IConnectionFactory.Password.get -> string
@@ -517,15 +498,6 @@ RabbitMQ.Client.IConnectionFactory.UserName.get -> string
RabbitMQ.Client.IConnectionFactory.UserName.set -> void
RabbitMQ.Client.IConnectionFactory.VirtualHost.get -> string
RabbitMQ.Client.IConnectionFactory.VirtualHost.set -> void
-RabbitMQ.Client.ICredentialsProvider
-RabbitMQ.Client.ICredentialsProvider.Name.get -> string
-RabbitMQ.Client.ICredentialsProvider.Password.get -> string
-RabbitMQ.Client.ICredentialsProvider.Refresh() -> void
-RabbitMQ.Client.ICredentialsProvider.UserName.get -> string
-RabbitMQ.Client.ICredentialsProvider.ValidUntil.get -> System.TimeSpan?
-RabbitMQ.Client.ICredentialsRefresher
-RabbitMQ.Client.ICredentialsRefresher.NotifyCredentialRefreshedAsync
-RabbitMQ.Client.ICredentialsRefresher.Unregister(RabbitMQ.Client.ICredentialsProvider provider) -> bool
RabbitMQ.Client.IEndpointResolver
RabbitMQ.Client.IEndpointResolver.All() -> System.Collections.Generic.IEnumerable
RabbitMQ.Client.INetworkConnection
@@ -610,7 +582,6 @@ RabbitMQ.Client.Logging.RabbitMqExceptionDetail.RabbitMqExceptionDetail(System.E
RabbitMQ.Client.Logging.RabbitMqExceptionDetail.StackTrace.get -> string
RabbitMQ.Client.Logging.RabbitMqExceptionDetail.Type.get -> string
RabbitMQ.Client.PlainMechanism
-RabbitMQ.Client.PlainMechanism.handleChallenge(byte[] challenge, RabbitMQ.Client.ConnectionConfig config) -> byte[]
RabbitMQ.Client.PlainMechanism.PlainMechanism() -> void
RabbitMQ.Client.PlainMechanismFactory
RabbitMQ.Client.PlainMechanismFactory.GetInstance() -> RabbitMQ.Client.IAuthMechanism
@@ -701,17 +672,6 @@ RabbitMQ.Client.SslOption.Version.set -> void
RabbitMQ.Client.TcpClientAdapter
RabbitMQ.Client.TcpClientAdapter.Dispose() -> void
RabbitMQ.Client.TcpClientAdapter.TcpClientAdapter(System.Net.Sockets.Socket socket) -> void
-RabbitMQ.Client.TimerBasedCredentialRefresher
-RabbitMQ.Client.TimerBasedCredentialRefresher.TimerBasedCredentialRefresher() -> void
-RabbitMQ.Client.TimerBasedCredentialRefresher.Unregister(RabbitMQ.Client.ICredentialsProvider provider) -> bool
-RabbitMQ.Client.TimerBasedCredentialRefresherEventSource
-RabbitMQ.Client.TimerBasedCredentialRefresherEventSource.AlreadyRegistered(string name) -> void
-RabbitMQ.Client.TimerBasedCredentialRefresherEventSource.RefreshedCredentials(string name, bool succesfully) -> void
-RabbitMQ.Client.TimerBasedCredentialRefresherEventSource.Registered(string name) -> void
-RabbitMQ.Client.TimerBasedCredentialRefresherEventSource.ScheduledTimer(string name, double interval) -> void
-RabbitMQ.Client.TimerBasedCredentialRefresherEventSource.TimerBasedCredentialRefresherEventSource() -> void
-RabbitMQ.Client.TimerBasedCredentialRefresherEventSource.TriggeredTimer(string name) -> void
-RabbitMQ.Client.TimerBasedCredentialRefresherEventSource.Unregistered(string name) -> void
RabbitMQ.Client.TopologyRecoveryExceptionHandler
RabbitMQ.Client.TopologyRecoveryExceptionHandler.BindingRecoveryExceptionCondition.get -> System.Func
RabbitMQ.Client.TopologyRecoveryExceptionHandler.BindingRecoveryExceptionCondition.set -> void
@@ -814,11 +774,9 @@ static RabbitMQ.Client.QueueDeclareOk.implicit operator string(RabbitMQ.Client.Q
static RabbitMQ.Client.RabbitMQActivitySource.UseRoutingKeyAsOperationName.get -> bool
static RabbitMQ.Client.RabbitMQActivitySource.UseRoutingKeyAsOperationName.set -> void
static RabbitMQ.Client.TcpClientAdapter.GetMatchingHost(System.Collections.Generic.IReadOnlyCollection addresses, System.Net.Sockets.AddressFamily addressFamily) -> System.Net.IPAddress
-static RabbitMQ.Client.TimerBasedCredentialRefresherEventSource.Log.get -> RabbitMQ.Client.TimerBasedCredentialRefresherEventSource
static readonly RabbitMQ.Client.CachedString.Empty -> RabbitMQ.Client.CachedString
static readonly RabbitMQ.Client.ConnectionFactory.DefaultAuthMechanisms -> System.Collections.Generic.IEnumerable
static readonly RabbitMQ.Client.ConnectionFactory.DefaultConnectionTimeout -> System.TimeSpan
-static readonly RabbitMQ.Client.ConnectionFactory.DefaultCredentialsRefresher -> RabbitMQ.Client.ICredentialsRefresher
static readonly RabbitMQ.Client.ConnectionFactory.DefaultHeartbeat -> System.TimeSpan
static readonly RabbitMQ.Client.Protocols.AMQP_0_9_1 -> RabbitMQ.Client.IProtocol
static readonly RabbitMQ.Client.Protocols.DefaultProtocol -> RabbitMQ.Client.IProtocol
@@ -883,8 +841,6 @@ virtual RabbitMQ.Client.TcpClientAdapter.ReceiveTimeout.set -> void
~RabbitMQ.Client.IConnectionFactory.CreateConnectionAsync(System.Collections.Generic.IEnumerable hostnames, string clientProvidedName, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
~RabbitMQ.Client.IConnectionFactory.CreateConnectionAsync(System.Collections.Generic.IEnumerable hostnames, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
~RabbitMQ.Client.IConnectionFactory.CreateConnectionAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
-~RabbitMQ.Client.ICredentialsRefresher.Register(RabbitMQ.Client.ICredentialsProvider provider, RabbitMQ.Client.ICredentialsRefresher.NotifyCredentialRefreshedAsync callback) -> RabbitMQ.Client.ICredentialsProvider
-~RabbitMQ.Client.TimerBasedCredentialRefresher.Register(RabbitMQ.Client.ICredentialsProvider provider, RabbitMQ.Client.ICredentialsRefresher.NotifyCredentialRefreshedAsync callback) -> RabbitMQ.Client.ICredentialsProvider
~RabbitMQ.Client.TopologyRecoveryExceptionHandler.BindingRecoveryExceptionHandlerAsync.get -> System.Func
~RabbitMQ.Client.TopologyRecoveryExceptionHandler.BindingRecoveryExceptionHandlerAsync.set -> void
~RabbitMQ.Client.TopologyRecoveryExceptionHandler.ConsumerRecoveryExceptionHandlerAsync.get -> System.Func
diff --git a/projects/RabbitMQ.Client/PublicAPI.Unshipped.txt b/projects/RabbitMQ.Client/PublicAPI.Unshipped.txt
index bbd247d625..e2c631ea30 100644
--- a/projects/RabbitMQ.Client/PublicAPI.Unshipped.txt
+++ b/projects/RabbitMQ.Client/PublicAPI.Unshipped.txt
@@ -1 +1,22 @@
+RabbitMQ.Client.BasicCredentialsProvider
+RabbitMQ.Client.BasicCredentialsProvider.BasicCredentialsProvider(string? name, string! userName, string! password) -> void
+RabbitMQ.Client.BasicCredentialsProvider.GetCredentialsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+RabbitMQ.Client.BasicCredentialsProvider.Name.get -> string!
RabbitMQ.Client.BasicProperties.BasicProperties(RabbitMQ.Client.IReadOnlyBasicProperties! input) -> void
+RabbitMQ.Client.ConnectionFactory.CredentialsProvider.get -> RabbitMQ.Client.ICredentialsProvider?
+RabbitMQ.Client.ConnectionFactory.CredentialsProvider.set -> void
+RabbitMQ.Client.Credentials
+RabbitMQ.Client.Credentials.Credentials(string! name, string! userName, string! password, System.TimeSpan? validUntil) -> void
+RabbitMQ.Client.Credentials.Name.get -> string!
+RabbitMQ.Client.Credentials.Password.get -> string!
+RabbitMQ.Client.Credentials.UserName.get -> string!
+RabbitMQ.Client.Credentials.ValidUntil.get -> System.TimeSpan?
+RabbitMQ.Client.ExternalMechanism.HandleChallengeAsync(byte[]? challenge, RabbitMQ.Client.ConnectionConfig! config, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+RabbitMQ.Client.IAuthMechanism.HandleChallengeAsync(byte[]? challenge, RabbitMQ.Client.ConnectionConfig! config, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+RabbitMQ.Client.IConnectionFactory.CredentialsProvider.get -> RabbitMQ.Client.ICredentialsProvider?
+RabbitMQ.Client.IConnectionFactory.CredentialsProvider.set -> void
+RabbitMQ.Client.ICredentialsProvider
+RabbitMQ.Client.ICredentialsProvider.GetCredentialsAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+RabbitMQ.Client.ICredentialsProvider.Name.get -> string!
+RabbitMQ.Client.PlainMechanism.HandleChallengeAsync(byte[]? challenge, RabbitMQ.Client.ConnectionConfig! config, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+readonly RabbitMQ.Client.ConnectionConfig.CredentialsProvider -> RabbitMQ.Client.ICredentialsProvider!
diff --git a/projects/RabbitMQ.Client/client/api/BasicCredentialsProvider.cs b/projects/RabbitMQ.Client/client/api/BasicCredentialsProvider.cs
index 97faa08d03..850def2523 100644
--- a/projects/RabbitMQ.Client/client/api/BasicCredentialsProvider.cs
+++ b/projects/RabbitMQ.Client/client/api/BasicCredentialsProvider.cs
@@ -30,6 +30,8 @@
//---------------------------------------------------------------------------
using System;
+using System.Threading;
+using System.Threading.Tasks;
namespace RabbitMQ.Client
{
@@ -38,39 +40,21 @@ public class BasicCredentialsProvider : ICredentialsProvider
private readonly string _name;
private readonly string _userName;
private readonly string _password;
+ private readonly Credentials _credentials;
public BasicCredentialsProvider(string? name, string userName, string password)
{
- _name = name ?? string.Empty;
+ _name = name ?? nameof(BasicCredentialsProvider);
_userName = userName ?? throw new ArgumentNullException(nameof(userName));
_password = password ?? throw new ArgumentNullException(nameof(password));
+ _credentials = new Credentials(_name, _userName, _password, null);
}
- public string Name
- {
- get { return _name; }
- }
-
- public string UserName
- {
- get { return _userName; }
- }
-
- public string Password
- {
- get { return _password; }
- }
-
- public Nullable ValidUntil
- {
- get
- {
- return null;
- }
- }
+ public string Name => _name;
- public void Refresh()
+ public Task GetCredentialsAsync(CancellationToken cancellationToken = default)
{
+ return Task.FromResult(_credentials);
}
}
}
diff --git a/projects/RabbitMQ.Client/client/api/ConnectionConfig.cs b/projects/RabbitMQ.Client/client/api/ConnectionConfig.cs
index 0dce4e8dd3..ab0a0d7665 100644
--- a/projects/RabbitMQ.Client/client/api/ConnectionConfig.cs
+++ b/projects/RabbitMQ.Client/client/api/ConnectionConfig.cs
@@ -58,11 +58,10 @@ public sealed class ConnectionConfig
public readonly string Password;
///
- /// Default CredentialsProvider implementation. If set, this
+ /// Default ICredentialsProvider implementation. If set, this
/// overrides UserName / Password
///
- public ICredentialsProvider CredentialsProvider;
- public ICredentialsRefresher CredentialsRefresher;
+ public readonly ICredentialsProvider CredentialsProvider;
///
/// SASL auth mechanisms to use.
@@ -145,7 +144,7 @@ public sealed class ConnectionConfig
internal readonly Func> FrameHandlerFactoryAsync;
internal ConnectionConfig(string virtualHost, string userName, string password,
- ICredentialsProvider? credentialsProvider, ICredentialsRefresher credentialsRefresher,
+ ICredentialsProvider? credentialsProvider,
IEnumerable authMechanisms,
IDictionary clientProperties, string? clientProvidedName,
ushort maxChannelCount, uint maxFrameSize, uint maxInboundMessageBodySize, bool topologyRecoveryEnabled,
@@ -157,7 +156,6 @@ internal ConnectionConfig(string virtualHost, string userName, string password,
UserName = userName;
Password = password;
CredentialsProvider = credentialsProvider ?? new BasicCredentialsProvider(clientProvidedName, userName, password);
- CredentialsRefresher = credentialsRefresher;
AuthMechanisms = authMechanisms;
ClientProperties = clientProperties;
ClientProvidedName = clientProvidedName;
diff --git a/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs b/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs
index 4858395357..cbcc5760d8 100644
--- a/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs
+++ b/projects/RabbitMQ.Client/client/api/ConnectionFactory.cs
@@ -161,8 +161,6 @@ public sealed class ConnectionFactory : ConnectionFactoryBase, IConnectionFactor
///
public static System.Net.Sockets.AddressFamily DefaultAddressFamily { get; set; }
- public static readonly ICredentialsRefresher DefaultCredentialsRefresher = new NoOpCredentialsRefresher();
-
///
/// Set to false to disable automatic connection recovery.
/// Defaults to true.
@@ -315,15 +313,10 @@ public AmqpTcpEndpoint Endpoint
public string Password { get; set; } = DefaultPass;
///
- /// CredentialsProvider used to obtain username and password.
+ /// ICredentialsProvider used to obtain username and password.
///
public ICredentialsProvider? CredentialsProvider { get; set; }
- ///
- /// Used to refresh credentials.
- ///
- public ICredentialsRefresher CredentialsRefresher { get; set; } = DefaultCredentialsRefresher;
-
///
/// Maximum channel number to ask for.
///
@@ -591,7 +584,6 @@ private ConnectionConfig CreateConfig(string? clientProvidedName)
UserName,
Password,
CredentialsProvider,
- CredentialsRefresher,
AuthMechanisms,
ClientProperties,
EnsureClientProvidedNameLength(clientProvidedName),
diff --git a/projects/RabbitMQ.Client/client/api/ExternalMechanism.cs b/projects/RabbitMQ.Client/client/api/ExternalMechanism.cs
index 364881f66f..ef54d96878 100644
--- a/projects/RabbitMQ.Client/client/api/ExternalMechanism.cs
+++ b/projects/RabbitMQ.Client/client/api/ExternalMechanism.cs
@@ -30,6 +30,8 @@
//---------------------------------------------------------------------------
using System;
+using System.Threading;
+using System.Threading.Tasks;
namespace RabbitMQ.Client
{
@@ -38,9 +40,10 @@ public class ExternalMechanism : IAuthMechanism
///
/// Handle one round of challenge-response.
///
- public byte[] handleChallenge(byte[]? challenge, ConnectionConfig config)
+ public Task HandleChallengeAsync(byte[]? challenge, ConnectionConfig config,
+ CancellationToken cancellationToken = default)
{
- return Array.Empty();
+ return Task.FromResult(Array.Empty());
}
}
}
diff --git a/projects/RabbitMQ.Client/client/api/IAuthMechanism.cs b/projects/RabbitMQ.Client/client/api/IAuthMechanism.cs
index 08c44e4af5..3f33195aa7 100644
--- a/projects/RabbitMQ.Client/client/api/IAuthMechanism.cs
+++ b/projects/RabbitMQ.Client/client/api/IAuthMechanism.cs
@@ -29,6 +29,9 @@
// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
//---------------------------------------------------------------------------
+using System.Threading;
+using System.Threading.Tasks;
+
namespace RabbitMQ.Client
{
///
@@ -39,6 +42,7 @@ public interface IAuthMechanism
///
/// Handle one round of challenge-response.
///
- byte[] handleChallenge(byte[]? challenge, ConnectionConfig config);
+ Task HandleChallengeAsync(byte[]? challenge, ConnectionConfig config,
+ CancellationToken cancellationToken = default);
}
}
diff --git a/projects/RabbitMQ.Client/client/api/IConnectionFactory.cs b/projects/RabbitMQ.Client/client/api/IConnectionFactory.cs
index 28d471c9e2..c6b37ec3ed 100644
--- a/projects/RabbitMQ.Client/client/api/IConnectionFactory.cs
+++ b/projects/RabbitMQ.Client/client/api/IConnectionFactory.cs
@@ -75,12 +75,9 @@ public interface IConnectionFactory
string VirtualHost { get; set; }
///
- /// Credentials provider. It is optional. When set, username and password
- /// are obtained thru this provider.
+ /// ICredentialsProvider used to obtain username and password.
///
- ICredentialsProvider? CredentialsProvider { get; set; }
-
- ICredentialsRefresher CredentialsRefresher { get; set; }
+ public ICredentialsProvider? CredentialsProvider { get; set; }
///
/// Sets or gets the AMQP Uri to be used for connections.
diff --git a/projects/RabbitMQ.Client/client/api/ICredentialsProvider.cs b/projects/RabbitMQ.Client/client/api/ICredentialsProvider.cs
index 9cd57a3991..1518331e90 100644
--- a/projects/RabbitMQ.Client/client/api/ICredentialsProvider.cs
+++ b/projects/RabbitMQ.Client/client/api/ICredentialsProvider.cs
@@ -29,25 +29,41 @@
// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
//---------------------------------------------------------------------------
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+
namespace RabbitMQ.Client
{
public interface ICredentialsProvider
{
string Name { get; }
- string UserName { get; }
- string Password { get; }
+ Task GetCredentialsAsync(CancellationToken cancellationToken = default);
+ }
+
+ public class Credentials
+ {
+ private readonly string _name;
+ private readonly string _userName;
+ private readonly string _password;
+ private readonly TimeSpan? _validUntil;
+
+ public Credentials(string name, string userName, string password, TimeSpan? validUntil)
+ {
+ _name = name;
+ _userName = userName;
+ _password = password;
+ _validUntil = validUntil;
+ }
+
+ public string Name => _name;
+ public string UserName => _userName;
+ public string Password => _password;
///
/// If credentials have an expiry time this property returns the interval.
/// Otherwise, it returns null.
///
- System.TimeSpan? ValidUntil { get; }
-
- ///
- /// Before the credentials are available, be it Username, Password or ValidUntil,
- /// the credentials must be obtained by calling this method.
- /// And to keep it up to date, this method must be called before the ValidUntil interval.
- ///
- void Refresh();
+ public TimeSpan? ValidUntil => _validUntil;
}
}
diff --git a/projects/RabbitMQ.Client/client/api/ICredentialsRefresher.cs b/projects/RabbitMQ.Client/client/api/ICredentialsRefresher.cs
deleted file mode 100644
index ea16d26b1f..0000000000
--- a/projects/RabbitMQ.Client/client/api/ICredentialsRefresher.cs
+++ /dev/null
@@ -1,156 +0,0 @@
-// This source code is dual-licensed under the Apache License, version
-// 2.0, and the Mozilla Public License, version 2.0.
-//
-// The APL v2.0:
-//
-//---------------------------------------------------------------------------
-// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//---------------------------------------------------------------------------
-//
-// The MPL v2.0:
-//
-//---------------------------------------------------------------------------
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at https://mozilla.org/MPL/2.0/.
-//
-// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
-//---------------------------------------------------------------------------
-
-using System;
-using System.Collections.Concurrent;
-using System.Diagnostics.CodeAnalysis;
-using System.Diagnostics.Tracing;
-using System.Threading.Tasks;
-namespace RabbitMQ.Client
-{
- public interface ICredentialsRefresher
- {
- ICredentialsProvider Register(ICredentialsProvider provider, NotifyCredentialRefreshedAsync callback);
- bool Unregister(ICredentialsProvider provider);
-
- delegate Task NotifyCredentialRefreshedAsync(bool successfully);
- }
-
- [EventSource(Name = "TimerBasedCredentialRefresher")]
- public class TimerBasedCredentialRefresherEventSource : EventSource
- {
- public static TimerBasedCredentialRefresherEventSource Log { get; } = new TimerBasedCredentialRefresherEventSource();
-
- [Event(1)]
- public void Registered(string name) => WriteEvent(1, "Registered", name);
- [Event(2)]
- public void Unregistered(string name) => WriteEvent(2, "UnRegistered", name);
- [Event(3)]
-#if NET6_0_OR_GREATER
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe")]
-#endif
- public void ScheduledTimer(string name, double interval) => WriteEvent(3, "ScheduledTimer", name, interval);
- [Event(4)]
- public void TriggeredTimer(string name) => WriteEvent(4, "TriggeredTimer", name);
- [Event(5)]
-#if NET6_0_OR_GREATER
- [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2026:RequiresUnreferencedCode", Justification = "Parameters to this method are primitive and are trimmer safe")]
-#endif
- public void RefreshedCredentials(string name, bool succesfully) => WriteEvent(5, "RefreshedCredentials", name, succesfully);
- [Event(6)]
- public void AlreadyRegistered(string name) => WriteEvent(6, "AlreadyRegistered", name);
- }
-
- public class TimerBasedCredentialRefresher : ICredentialsRefresher
- {
- private readonly ConcurrentDictionary _registrations = new ConcurrentDictionary();
-
- public ICredentialsProvider Register(ICredentialsProvider provider, ICredentialsRefresher.NotifyCredentialRefreshedAsync callback)
- {
- if (!provider.ValidUntil.HasValue || provider.ValidUntil.Value.Equals(TimeSpan.Zero))
- {
- return provider;
- }
-
- if (_registrations.TryAdd(provider, scheduleTimer(provider, callback)))
- {
- TimerBasedCredentialRefresherEventSource.Log.Registered(provider.Name);
- }
- else
- {
- TimerBasedCredentialRefresherEventSource.Log.AlreadyRegistered(provider.Name);
- }
-
- return provider;
- }
-
- public bool Unregister(ICredentialsProvider provider)
- {
- if (_registrations.TryRemove(provider, out System.Timers.Timer? timer))
- {
- try
- {
- TimerBasedCredentialRefresherEventSource.Log.Unregistered(provider.Name);
- timer.Stop();
- }
- finally
- {
- timer.Dispose();
- }
- return true;
- }
- else
- {
- return false;
- }
- }
-
- private System.Timers.Timer scheduleTimer(ICredentialsProvider provider, ICredentialsRefresher.NotifyCredentialRefreshedAsync callback)
- {
- System.Timers.Timer timer = new System.Timers.Timer();
- timer.Interval = provider.ValidUntil!.Value.TotalMilliseconds * (1.0 - (1 / 3.0));
- timer.Elapsed += (o, e) =>
- {
- TimerBasedCredentialRefresherEventSource.Log.TriggeredTimer(provider.Name);
- try
- {
- provider.Refresh();
- scheduleTimer(provider, callback);
- callback.Invoke(provider.Password != null);
- TimerBasedCredentialRefresherEventSource.Log.RefreshedCredentials(provider.Name, true);
- }
- catch (Exception)
- {
- callback.Invoke(false);
- TimerBasedCredentialRefresherEventSource.Log.RefreshedCredentials(provider.Name, false);
- }
-
- };
- timer.Enabled = true;
- timer.AutoReset = false;
- TimerBasedCredentialRefresherEventSource.Log.ScheduledTimer(provider.Name, timer.Interval);
- return timer;
- }
- }
-
- class NoOpCredentialsRefresher : ICredentialsRefresher
- {
- public ICredentialsProvider Register(ICredentialsProvider provider, ICredentialsRefresher.NotifyCredentialRefreshedAsync callback)
- {
- return provider;
- }
-
- public bool Unregister(ICredentialsProvider provider)
- {
- return false;
- }
- }
-}
diff --git a/projects/RabbitMQ.Client/client/api/PlainMechanism.cs b/projects/RabbitMQ.Client/client/api/PlainMechanism.cs
index ca2218f5d4..30f45b045c 100644
--- a/projects/RabbitMQ.Client/client/api/PlainMechanism.cs
+++ b/projects/RabbitMQ.Client/client/api/PlainMechanism.cs
@@ -30,14 +30,20 @@
//---------------------------------------------------------------------------
using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
namespace RabbitMQ.Client
{
public class PlainMechanism : IAuthMechanism
{
- public byte[] handleChallenge(byte[]? challenge, ConnectionConfig config)
+ public async Task HandleChallengeAsync(byte[]? challenge, ConnectionConfig config,
+ CancellationToken cancellationToken = default)
{
- return Encoding.UTF8.GetBytes($"\0{config.CredentialsProvider.UserName}\0{config.CredentialsProvider.Password}");
+ ICredentialsProvider credentialsProvider = config.CredentialsProvider;
+ Credentials credentials = await credentialsProvider.GetCredentialsAsync(cancellationToken)
+ .ConfigureAwait(false);
+ return Encoding.UTF8.GetBytes($"\0{credentials.UserName}\0{credentials.Password}");
}
}
}
diff --git a/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs b/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs
index adb1ee8cc9..2cdc180df2 100644
--- a/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs
+++ b/projects/RabbitMQ.Client/client/impl/Connection.Commands.cs
@@ -133,7 +133,8 @@ await FinishCloseAsync(cancellationToken)
byte[]? challenge = null;
do
{
- byte[] response = mechanism.handleChallenge(challenge, _config);
+ byte[] response = await mechanism.HandleChallengeAsync(challenge, _config)
+ .ConfigureAwait(false);
ConnectionSecureOrTune res;
if (challenge is null)
{
@@ -183,31 +184,12 @@ await _channel0.ConnectionTuneOkAsync(channelMax, frameMax, (ushort)Heartbeat.To
.ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
- MaybeStartCredentialRefresher();
// now we can start heartbeat timers
cancellationToken.ThrowIfCancellationRequested();
MaybeStartHeartbeatTimers();
}
- private void MaybeStartCredentialRefresher()
- {
- if (_config.CredentialsProvider.ValidUntil != null)
- {
- _config.CredentialsRefresher.Register(_config.CredentialsProvider, NotifyCredentialRefreshedAsync);
- }
- }
-
- private async Task NotifyCredentialRefreshedAsync(bool succesfully)
- {
- if (succesfully)
- {
- using var cts = new CancellationTokenSource(InternalConstants.DefaultConnectionCloseTimeout);
- await UpdateSecretAsync(_config.CredentialsProvider.Password, "Token refresh", cts.Token)
- .ConfigureAwait(false);
- }
- }
-
private IAuthMechanismFactory GetAuthMechanismFactory(string supportedMechanismNames)
{
// Our list is in order of preference, the server one is not.
diff --git a/projects/Test/Common/TestConnectionRecoveryBase.cs b/projects/Test/Common/TestConnectionRecoveryBase.cs
index cf27d06d13..59ad8f11a5 100644
--- a/projects/Test/Common/TestConnectionRecoveryBase.cs
+++ b/projects/Test/Common/TestConnectionRecoveryBase.cs
@@ -40,8 +40,9 @@
namespace Test
{
- public class TestConnectionRecoveryBase : IntegrationFixture
+ public class TestConnectionRecoveryBase : IntegrationFixture, IDisposable
{
+ private readonly Util _util;
protected readonly byte[] _messageBody;
protected const ushort TotalMessageCount = 16384;
protected const ushort CloseAtCount = 16;
@@ -49,9 +50,12 @@ public class TestConnectionRecoveryBase : IntegrationFixture
public TestConnectionRecoveryBase(ITestOutputHelper output)
: base(output)
{
+ _util = new Util(output);
_messageBody = GetRandomBody(4096);
}
+ public void Dispose() => _util.Dispose();
+
protected Task AssertConsumerCountAsync(string q, int count)
{
return WithTemporaryChannelAsync(async ch =>
@@ -166,7 +170,7 @@ internal async Task CreateAutorecoveringConnectionWith
protected Task CloseConnectionAsync(IConnection conn)
{
- return Util.CloseConnectionAsync(conn);
+ return _util.CloseConnectionAsync(conn.ClientProvidedName);
}
protected Task CloseAndWaitForRecoveryAsync()
diff --git a/projects/Test/Common/Util.cs b/projects/Test/Common/Util.cs
index e9afa4edfd..db3382edde 100644
--- a/projects/Test/Common/Util.cs
+++ b/projects/Test/Common/Util.cs
@@ -4,45 +4,48 @@
using System.Linq;
using System.Threading.Tasks;
using EasyNetQ.Management.Client;
-using RabbitMQ.Client;
+using Xunit.Abstractions;
namespace Test
{
- public static class Util
+ public class Util : IDisposable
{
- private static readonly ManagementClient s_managementClient;
+ private readonly ITestOutputHelper _output;
+ private readonly ManagementClient _managementClient;
private static readonly bool s_isWindows = false;
static Util()
{
- var managementUri = new Uri("http://localhost:15672");
- s_managementClient = new ManagementClient(managementUri, "guest", "guest");
s_isWindows = InitIsWindows();
}
- public static string Now => DateTime.UtcNow.ToString("o", CultureInfo.InvariantCulture);
-
- public static bool IsWindows => s_isWindows;
+ public Util(ITestOutputHelper output) : this(output, "guest", "guest")
+ {
+ }
- private static bool InitIsWindows()
+ public Util(ITestOutputHelper output, string managementUsername, string managementPassword)
{
- PlatformID platform = Environment.OSVersion.Platform;
- if (platform == PlatformID.Win32NT)
+ _output = output;
+
+ if (string.IsNullOrEmpty(managementUsername))
{
- return true;
+ managementUsername = "guest";
}
- string os = Environment.GetEnvironmentVariable("OS");
- if (os != null)
+ if (string.IsNullOrEmpty(managementPassword))
{
- os = os.Trim();
- return os == "Windows_NT";
+ throw new ArgumentNullException(nameof(managementPassword));
}
- return false;
+ var managementUri = new Uri("http://localhost:15672");
+ _managementClient = new ManagementClient(managementUri, managementUsername, managementPassword);
}
- public static async Task CloseConnectionAsync(IConnection conn)
+ public static string Now => DateTime.UtcNow.ToString("s", CultureInfo.InvariantCulture);
+
+ public static bool IsWindows => s_isWindows;
+
+ public async Task CloseConnectionAsync(string connectionClientProvidedName)
{
ushort tries = 1;
EasyNetQ.Management.Client.Model.Connection connectionToClose = null;
@@ -61,11 +64,11 @@ public static async Task CloseConnectionAsync(IConnection conn)
await Task.Delay(TimeSpan.FromSeconds(delaySeconds));
- connections = await s_managementClient.GetConnectionsAsync();
+ connections = await _managementClient.GetConnectionsAsync();
} while (connections.Count == 0);
connectionToClose = connections.Where(c0 =>
- string.Equals((string)c0.ClientProperties["connection_name"], conn.ClientProvidedName,
+ string.Equals((string)c0.ClientProperties["connection_name"], connectionClientProvidedName,
StringComparison.InvariantCultureIgnoreCase)).FirstOrDefault();
}
catch (ArgumentNullException)
@@ -79,7 +82,7 @@ public static async Task CloseConnectionAsync(IConnection conn)
{
try
{
- await s_managementClient.CloseConnectionAsync(connectionToClose);
+ await _managementClient.CloseConnectionAsync(connectionToClose);
return;
}
catch (UnexpectedHttpStatusCodeException)
@@ -91,8 +94,29 @@ public static async Task CloseConnectionAsync(IConnection conn)
if (connectionToClose == null)
{
- throw new InvalidOperationException($"Could not delete connection: '{conn.ClientProvidedName}'");
+ _output.WriteLine("{0} [WARNING] could not find/delete connection: '{1}'",
+ Now, connectionClientProvidedName);
}
}
+
+ public void Dispose() => _managementClient.Dispose();
+
+ private static bool InitIsWindows()
+ {
+ PlatformID platform = Environment.OSVersion.Platform;
+ if (platform == PlatformID.Win32NT)
+ {
+ return true;
+ }
+
+ string os = Environment.GetEnvironmentVariable("OS");
+ if (os != null)
+ {
+ os = os.Trim();
+ return os == "Windows_NT";
+ }
+
+ return false;
+ }
}
}
diff --git a/projects/Test/Integration/TestQueueDeclare.cs b/projects/Test/Integration/TestQueueDeclare.cs
index 5b4f8ae4d3..8ba26841b7 100644
--- a/projects/Test/Integration/TestQueueDeclare.cs
+++ b/projects/Test/Integration/TestQueueDeclare.cs
@@ -46,7 +46,7 @@ public TestQueueDeclare(ITestOutputHelper output) : base(output)
}
[Fact]
- public async void TestQueueDeclareAsync()
+ public async Task TestQueueDeclareAsync()
{
string q = GenerateQueueName();
@@ -58,7 +58,7 @@ public async void TestQueueDeclareAsync()
}
[Fact]
- public async void TestConcurrentQueueDeclareAndBindAsync()
+ public async Task TestConcurrentQueueDeclareAndBindAsync()
{
bool sawShutdown = false;
diff --git a/projects/Test/OAuth2/MockOAuth2Client.cs b/projects/Test/OAuth2/MockOAuth2Client.cs
new file mode 100644
index 0000000000..0daecca708
--- /dev/null
+++ b/projects/Test/OAuth2/MockOAuth2Client.cs
@@ -0,0 +1,103 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+using System.Threading.Tasks;
+using RabbitMQ.Client.OAuth2;
+using Xunit.Abstractions;
+
+namespace OAuth2Test
+{
+ public class MockOAuth2Client : IOAuth2Client
+ {
+ private readonly ITestOutputHelper _testOutputHelper;
+ private IToken? _refreshToken;
+ private IToken? _requestToken;
+
+ public MockOAuth2Client(ITestOutputHelper testOutputHelper)
+ {
+ _testOutputHelper = testOutputHelper;
+ }
+
+ public IToken? RefreshTokenValue
+ {
+ get { return _refreshToken; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _refreshToken = value;
+ }
+ }
+
+ public IToken? RequestTokenValue
+ {
+ get { return _requestToken; }
+ set
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ _requestToken = value;
+ }
+ }
+
+ public Task RequestTokenAsync(CancellationToken cancellationToken = default)
+ {
+ if (_requestToken is null)
+ {
+ throw new NullReferenceException();
+ }
+
+ return Task.FromResult(_requestToken);
+ }
+
+ public Task RefreshTokenAsync(IToken initialToken, CancellationToken cancellationToken = default)
+ {
+ Debug.Assert(Object.ReferenceEquals(_requestToken, initialToken));
+
+ if (_refreshToken is null)
+ {
+ throw new NullReferenceException();
+
+ }
+ return Task.FromResult(_refreshToken);
+ }
+ }
+
+}
diff --git a/projects/Test/OAuth2/OAuth2.csproj b/projects/Test/OAuth2/OAuth2.csproj
index d958da4c66..45699f5381 100644
--- a/projects/Test/OAuth2/OAuth2.csproj
+++ b/projects/Test/OAuth2/OAuth2.csproj
@@ -1,4 +1,4 @@
-
+
net6.0;net472
@@ -16,12 +16,14 @@
../../rabbit.snk
true
true
- 7.3
+ 8.0
+ enable
+
diff --git a/projects/Test/OAuth2/OAuth2Options.cs b/projects/Test/OAuth2/OAuth2Options.cs
new file mode 100644
index 0000000000..10c173f14f
--- /dev/null
+++ b/projects/Test/OAuth2/OAuth2Options.cs
@@ -0,0 +1,119 @@
+using System;
+
+namespace OAuth2Test
+{
+ public enum Mode
+ {
+ uaa,
+ keycloak
+ }
+
+ public abstract class OAuth2OptionsBase
+ {
+ protected readonly Mode _mode;
+
+ public OAuth2OptionsBase(Mode mode)
+ {
+ _mode = mode;
+ }
+
+ public string Name
+ {
+ get
+ {
+ return _mode switch
+ {
+ Mode.uaa => "uaa",
+ Mode.keycloak => "keycloak",
+ _ => throw new InvalidOperationException(),
+ };
+ }
+ }
+
+ public string Scope
+ {
+ get
+ {
+ return _mode switch
+ {
+ Mode.uaa => string.Empty,
+ Mode.keycloak => "rabbitmq:configure:*/* rabbitmq:read:*/* rabbitmq:write:*/*",
+ _ => throw new InvalidOperationException(),
+ };
+ }
+ }
+
+ public string TokenEndpoint // => _mode switch
+ {
+ get
+ {
+ return _mode switch
+ {
+ Mode.uaa => "http://localhost:8080/oauth/token",
+ Mode.keycloak => "http://localhost:8080/realms/test/protocol/openid-connect/token",
+ _ => throw new InvalidOperationException(),
+ };
+ }
+ }
+
+ public abstract string ClientId { get; }
+ public abstract string ClientSecret { get; }
+
+ public static int TokenExpiresInSeconds => 60;
+ }
+
+ public class OAuth2ProducerOptions : OAuth2OptionsBase
+ {
+ public OAuth2ProducerOptions(Mode mode) : base(mode)
+ {
+ }
+
+ public override string ClientId => "producer";
+
+ public override string ClientSecret
+ {
+ get
+ {
+ return _mode switch
+ {
+ Mode.uaa => "producer_secret",
+ Mode.keycloak => "kbOFBXI9tANgKUq8vXHLhT6YhbivgXxn",
+ _ => throw new InvalidOperationException(),
+ };
+ }
+ }
+ }
+
+ public class OAuth2HttpApiOptions : OAuth2OptionsBase
+ {
+ public OAuth2HttpApiOptions(Mode mode) : base(mode)
+ {
+ }
+
+ public override string ClientId
+ {
+ get
+ {
+ return _mode switch
+ {
+ Mode.uaa => "mgt_api_client",
+ Mode.keycloak => "mgt_api_client",
+ _ => throw new InvalidOperationException(),
+ };
+ }
+ }
+
+ public override string ClientSecret
+ {
+ get
+ {
+ return _mode switch
+ {
+ Mode.uaa => "mgt_api_client",
+ Mode.keycloak => "LWOuYqJ8gjKg3D2U8CJZDuID3KiRZVDa",
+ _ => throw new InvalidOperationException(),
+ };
+ }
+ }
+ }
+}
diff --git a/projects/Test/OAuth2/RequestFormMatcher.cs b/projects/Test/OAuth2/RequestFormMatcher.cs
index 9d3e518069..ab6e47ba5c 100644
--- a/projects/Test/OAuth2/RequestFormMatcher.cs
+++ b/projects/Test/OAuth2/RequestFormMatcher.cs
@@ -31,13 +31,14 @@
using System;
using System.Collections.Specialized;
+using System.Web;
using Xunit;
namespace OAuth2Test
{
public class RequestFormMatcher
{
- private NameValueCollection _expected = new NameValueCollection();
+ private readonly NameValueCollection _expected = new NameValueCollection();
public RequestFormMatcher WithParam(string key, string value)
{
@@ -45,12 +46,17 @@ public RequestFormMatcher WithParam(string key, string value)
return this;
}
- public Func Matcher()
+ public Func Matcher()
{
return (body) =>
{
- NameValueCollection actual = System.Web.HttpUtility.ParseQueryString(body);
- Assert.Equal(actual.Count, _expected.Count);
+ if (body is null)
+ {
+ throw new ArgumentNullException(nameof(body));
+ }
+
+ NameValueCollection actual = HttpUtility.ParseQueryString(body);
+ Assert.Equal(_expected.Count, actual.Count);
foreach (string k in actual.Keys)
{
Assert.NotNull(_expected[k]);
diff --git a/projects/Test/OAuth2/TestCredentialsRefresher.cs b/projects/Test/OAuth2/TestCredentialsRefresher.cs
new file mode 100644
index 0000000000..d5c46b8918
--- /dev/null
+++ b/projects/Test/OAuth2/TestCredentialsRefresher.cs
@@ -0,0 +1,162 @@
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//---------------------------------------------------------------------------
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
+using RabbitMQ.Client.OAuth2;
+using Xunit;
+using Xunit.Abstractions;
+
+namespace OAuth2Test
+{
+ public class MockCredentialsProvider : ICredentialsProvider
+ {
+ private readonly ITestOutputHelper _testOutputHelper;
+ private readonly TimeSpan _validUntil;
+ private readonly Exception? _maybeGetCredentialsException;
+
+ public MockCredentialsProvider(ITestOutputHelper testOutputHelper, TimeSpan validUntil,
+ Exception? maybeGetCredentialsException = null)
+ {
+ _testOutputHelper = testOutputHelper;
+ _validUntil = validUntil;
+ _maybeGetCredentialsException = maybeGetCredentialsException;
+ }
+
+ public string Name => GetType().Name;
+
+ public Task GetCredentialsAsync(CancellationToken cancellationToken = default)
+ {
+ if (_maybeGetCredentialsException is null)
+ {
+ var creds = new Credentials(this.GetType().Name, "guest", "guest", _validUntil);
+ return Task.FromResult(creds);
+ }
+ else
+ {
+ throw _maybeGetCredentialsException;
+ }
+ }
+ }
+
+ public class TestCredentialsRefresher
+ {
+ private readonly ITestOutputHelper _testOutputHelper;
+
+ public TestCredentialsRefresher(ITestOutputHelper testOutputHelper)
+ {
+ _testOutputHelper = testOutputHelper;
+ }
+
+ [Fact]
+ public async Task TestRefreshToken()
+ {
+ var expectedValidUntil = TimeSpan.FromSeconds(1);
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var credentialsProvider = new MockCredentialsProvider(_testOutputHelper, expectedValidUntil);
+
+ Task cb(Credentials? argCreds, Exception? ex, CancellationToken argToken)
+ {
+ if (argCreds is null)
+ {
+ tcs.SetException(new NullReferenceException("argCreds is null, huh?"));
+ }
+ else
+ {
+ tcs.SetResult(argCreds);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ Credentials credentials;
+ using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
+ {
+ using (CancellationTokenRegistration ctr = cts.Token.Register(() => tcs.TrySetCanceled()))
+ {
+ var credentialRefresher = new CredentialsRefresher(credentialsProvider, cb, cts.Token);
+ credentials = await tcs.Task;
+ }
+ }
+
+ Assert.Equal(nameof(MockCredentialsProvider), credentials.Name);
+ Assert.Equal("guest", credentials.UserName);
+ Assert.Equal("guest", credentials.Password);
+ Assert.Equal(expectedValidUntil, credentials.ValidUntil);
+ }
+
+ [Fact]
+ public async Task TestRefreshTokenFailed()
+ {
+ string exceptionMessage = nameof(TestCredentialsRefresher);
+ var expectedException = new Exception(exceptionMessage);
+
+ var expectedValidUntil = TimeSpan.FromSeconds(1);
+ var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
+ var credentialsProvider = new MockCredentialsProvider(_testOutputHelper, expectedValidUntil, expectedException);
+
+ ushort callbackCount = 0;
+ Task cb(Credentials? argCreds, Exception? ex, CancellationToken argToken)
+ {
+ callbackCount++;
+
+ if (ex != null)
+ {
+ tcs.SetException(ex);
+ }
+
+ return Task.CompletedTask;
+ }
+
+ Credentials? credentials = null;
+ using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
+ {
+ using (CancellationTokenRegistration ctr = cts.Token.Register(() => tcs.TrySetCanceled()))
+ {
+ var credentialRefresher = new CredentialsRefresher(credentialsProvider, cb, cts.Token);
+ try
+ {
+ credentials = await tcs.Task;
+ }
+ catch (Exception ex)
+ {
+ Assert.Same(expectedException, ex);
+ }
+ }
+ }
+
+ Assert.Null(credentials);
+ Assert.Equal(1, callbackCount);
+ }
+ }
+}
diff --git a/projects/Test/OAuth2/TestOAuth2.cs b/projects/Test/OAuth2/TestOAuth2.cs
index f42abeb718..9f87f90b1b 100644
--- a/projects/Test/OAuth2/TestOAuth2.cs
+++ b/projects/Test/OAuth2/TestOAuth2.cs
@@ -1,99 +1,47 @@
-using System;
+// This source code is dual-licensed under the Apache License, version
+// 2.0, and the Mozilla Public License, version 2.0.
+//
+// The APL v2.0:
+//
+//---------------------------------------------------------------------------
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//---------------------------------------------------------------------------
+//
+// The MPL v2.0:
+//
+//---------------------------------------------------------------------------
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at https://mozilla.org/MPL/2.0/.
+//
+// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
+//---------------------------------------------------------------------------
+
+using System;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.OAuth2;
+using Test;
using Xunit;
using Xunit.Abstractions;
namespace OAuth2Test
{
- public enum Mode
- {
- uaa,
- keycloak
- }
-
- public class OAuth2Options
- {
- private readonly Mode _mode;
-
- public OAuth2Options(Mode mode)
- {
- _mode = mode;
- }
-
- public string Name
- {
- get
- {
- switch (_mode)
- {
- case Mode.uaa:
- return "uaa";
- case Mode.keycloak:
- return "keycloak";
- default:
- throw new InvalidOperationException();
- }
- }
- }
-
- public string ClientId => "producer";
-
- public string ClientSecret
- {
- get
- {
- switch (_mode)
- {
- case Mode.uaa:
- return "producer_secret";
- case Mode.keycloak:
- return "kbOFBXI9tANgKUq8vXHLhT6YhbivgXxn";
- default:
- throw new InvalidOperationException();
- }
- }
- }
-
- public string Scope
- {
- get
- {
- switch (_mode)
- {
- case Mode.uaa:
- return string.Empty;
- case Mode.keycloak:
- return "rabbitmq:configure:*/* rabbitmq:read:*/* rabbitmq:write:*/*";
- default:
- throw new InvalidOperationException();
- }
- }
- }
-
- public string TokenEndpoint // => _mode switch
- {
- get
- {
- switch (_mode)
- {
- case Mode.uaa:
- return "http://localhost:8080/oauth/token";
- case Mode.keycloak:
- return "http://localhost:8080/realms/test/protocol/openid-connect/token";
- default:
- throw new InvalidOperationException();
- }
- }
- }
-
- public int TokenExpiresInSeconds => 60;
- }
-
public class TestOAuth2 : IAsyncLifetime
{
private const string Exchange = "test_direct";
@@ -101,8 +49,13 @@ public class TestOAuth2 : IAsyncLifetime
private readonly SemaphoreSlim _doneEvent = new SemaphoreSlim(0, 1);
private readonly ITestOutputHelper _testOutputHelper;
private readonly IConnectionFactory _connectionFactory;
- private IConnection _connection;
private readonly int _tokenExpiresInSeconds;
+ private readonly OAuth2ClientCredentialsProvider _producerCredentialsProvider;
+ private readonly OAuth2ClientCredentialsProvider _httpApiCredentialsProvider;
+ private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
+
+ private IConnection? _connection;
+ private CredentialsRefresher? _credentialsRefresher;
public TestOAuth2(ITestOutputHelper testOutputHelper)
{
@@ -110,43 +63,100 @@ public TestOAuth2(ITestOutputHelper testOutputHelper)
string modeStr = Environment.GetEnvironmentVariable("OAUTH2_MODE") ?? "uaa";
Mode mode = (Mode)Enum.Parse(typeof(Mode), modeStr.ToLowerInvariant());
- var options = new OAuth2Options(mode);
+
+ var producerOptions = new OAuth2ProducerOptions(mode);
+ _producerCredentialsProvider = GetCredentialsProvider(producerOptions);
+
+ var httpApiOptions = new OAuth2HttpApiOptions(mode);
+ _httpApiCredentialsProvider = GetCredentialsProvider(httpApiOptions);
_connectionFactory = new ConnectionFactory
{
AutomaticRecoveryEnabled = true,
- CredentialsProvider = GetCredentialsProvider(options),
- CredentialsRefresher = GetCredentialsRefresher(),
+ CredentialsProvider = _producerCredentialsProvider,
ClientProvidedName = nameof(TestOAuth2)
};
- _tokenExpiresInSeconds = options.TokenExpiresInSeconds;
+ _tokenExpiresInSeconds = OAuth2OptionsBase.TokenExpiresInSeconds;
}
public async Task InitializeAsync()
{
- _connection = await _connectionFactory.CreateConnectionAsync(CancellationToken.None);
+ _connection = await _connectionFactory.CreateConnectionAsync(_cancellationTokenSource.Token);
+
+ _connection.ConnectionShutdown += (sender, ea) =>
+ {
+ _testOutputHelper.WriteLine("{0} [WARNING] connection shutdown!", DateTime.Now);
+ };
+
+ _connection.ConnectionRecoveryError += (sender, ea) =>
+ {
+ _testOutputHelper.WriteLine("{0} [ERROR] connection recovery error: {1}",
+ DateTime.Now, ea.Exception);
+ };
+
+ _connection.RecoverySucceeded += (sender, ea) =>
+ {
+ _testOutputHelper.WriteLine("{0} [INFO] connection recovery succeeded", DateTime.Now);
+ };
+
+ _credentialsRefresher = new CredentialsRefresher(_producerCredentialsProvider,
+ OnCredentialsRefreshedAsync,
+ _cancellationTokenSource.Token);
}
public async Task DisposeAsync()
{
try
{
- await _connection.CloseAsync();
+ _cancellationTokenSource.Cancel();
+
+ if (_connection != null)
+ {
+ await _connection.CloseAsync();
+ }
}
finally
{
_doneEvent.Dispose();
- _connection.Dispose();
+ _producerCredentialsProvider.Dispose();
+ _connection?.Dispose();
+ }
+ }
+
+ private Task OnCredentialsRefreshedAsync(Credentials? credentials, Exception? exception,
+ CancellationToken cancellationToken = default)
+ {
+ if (_connection is null)
+ {
+ _testOutputHelper.WriteLine("connection is unexpectedly null!");
+ Assert.Fail("_connection is unexpectedly null!");
+ }
+
+ if (exception != null)
+ {
+ _testOutputHelper.WriteLine("exception is unexpectedly not-null: {0}", exception);
+ Assert.Fail($"exception is unexpectedly not-null: {exception}");
}
+
+ if (credentials is null)
+ {
+ _testOutputHelper.WriteLine("credentials arg is unexpectedly null!");
+ Assert.Fail("credentials arg is unexpectedly null!");
+ }
+
+ return _connection.UpdateSecretAsync(credentials.Password, "Token refresh", cancellationToken);
}
[Fact]
- public async void IntegrationTest()
+ public async Task IntegrationTest()
{
- using (IChannel publishChannel = await DeclarePublisherAsync())
+ Util? closeConnectionUtil = null;
+ Task? closeConnectionTask = null;
+
+ using (IChannel publishChannel = await DeclarePublishChannelAsync())
{
- using (IChannel consumeChannel = await DeclareConsumerAsync())
+ using (IChannel consumeChannel = await DeclareConsumeChannelAsync())
{
await PublishAsync(publishChannel);
await ConsumeAsync(consumeChannel);
@@ -155,10 +165,38 @@ public async void IntegrationTest()
{
for (int i = 0; i < 4; i++)
{
- _testOutputHelper.WriteLine("Wait until Token expires. Attempt #" + (i + 1));
-
- await Task.Delay(TimeSpan.FromSeconds(_tokenExpiresInSeconds + 10));
- _testOutputHelper.WriteLine("Resuming ..");
+ var delaySpan = TimeSpan.FromSeconds(_tokenExpiresInSeconds + 10);
+ _testOutputHelper.WriteLine("{0} [INFO] wait '{1}' until Token expires. Attempt #{1}",
+ DateTime.Now, delaySpan, (i + 1));
+
+ if (i == 1)
+ {
+ async Task CloseConnection()
+ {
+ Assert.NotNull(_connection);
+ Credentials httpApiCredentials = await _httpApiCredentialsProvider.GetCredentialsAsync();
+ closeConnectionUtil = new Util(_testOutputHelper, "mgt_api_client", httpApiCredentials.Password);
+ await closeConnectionUtil.CloseConnectionAsync(_connection.ClientProvidedName);
+ }
+
+ closeConnectionTask = Task.Run(CloseConnection);
+ }
+
+ await Task.Delay(delaySpan);
+
+ if (closeConnectionTask != null)
+ {
+ await closeConnectionTask;
+ closeConnectionTask = null;
+
+ if (closeConnectionUtil != null)
+ {
+ closeConnectionUtil.Dispose();
+ closeConnectionUtil = null;
+ }
+ }
+
+ _testOutputHelper.WriteLine("{0} [INFO] Resuming ...", DateTime.Now);
await PublishAsync(publishChannel);
await ConsumeAsync(consumeChannel);
@@ -177,22 +215,23 @@ public async void IntegrationTest()
}
[Fact]
- public async void SecondConnectionCrashes_GH1429()
+ public async Task SecondConnectionCrashes_GH1429()
{
// https://github.com/rabbitmq/rabbitmq-dotnet-client/issues/1429
IConnection secondConnection = await _connectionFactory.CreateConnectionAsync(CancellationToken.None);
secondConnection.Dispose();
}
- private async Task DeclarePublisherAsync()
+ private async Task DeclarePublishChannelAsync()
{
- IChannel publisher = await _connection.CreateChannelAsync();
- await publisher.ConfirmSelectAsync();
- await publisher.ExchangeDeclareAsync("test_direct", ExchangeType.Direct, true, false);
- return publisher;
+ Assert.NotNull(_connection);
+ IChannel publishChannel = await _connection.CreateChannelAsync();
+ await publishChannel.ConfirmSelectAsync();
+ await publishChannel.ExchangeDeclareAsync("test_direct", ExchangeType.Direct, true, false);
+ return publishChannel;
}
- private async Task PublishAsync(IChannel publisher)
+ private async Task PublishAsync(IChannel publishChannel)
{
const string message = "Hello World!";
@@ -202,32 +241,33 @@ private async Task PublishAsync(IChannel publisher)
AppId = "oauth2",
};
- await publisher.BasicPublishAsync(exchange: Exchange, routingKey: "hello", basicProperties: properties, body: body);
+ await publishChannel.BasicPublishAsync(exchange: Exchange, routingKey: "hello", basicProperties: properties, body: body);
_testOutputHelper.WriteLine("Sent message");
- await publisher.WaitForConfirmsOrDieAsync();
+ await publishChannel.WaitForConfirmsOrDieAsync();
_testOutputHelper.WriteLine("Confirmed Sent message");
}
- private async ValueTask DeclareConsumerAsync()
+ private async ValueTask DeclareConsumeChannelAsync()
{
- IChannel subscriber = await _connection.CreateChannelAsync();
- await subscriber.QueueDeclareAsync(queue: "testqueue", true, false, false);
- await subscriber.QueueBindAsync("testqueue", Exchange, "hello");
- return subscriber;
+ Assert.NotNull(_connection);
+ IChannel consumeChannel = await _connection.CreateChannelAsync();
+ await consumeChannel.QueueDeclareAsync(queue: "testqueue", true, false, false);
+ await consumeChannel.QueueBindAsync("testqueue", Exchange, "hello");
+ return consumeChannel;
}
- private async Task ConsumeAsync(IChannel subscriber)
+ private async Task ConsumeAsync(IChannel consumeChannel)
{
- var asyncListener = new AsyncEventingBasicConsumer(subscriber);
+ var asyncListener = new AsyncEventingBasicConsumer(consumeChannel);
asyncListener.Received += AsyncListener_Received;
- string consumerTag = await subscriber.BasicConsumeAsync("testqueue", true, "testconsumer", asyncListener);
- await _doneEvent.WaitAsync(TimeSpan.FromMilliseconds(500));
+ string consumerTag = await consumeChannel.BasicConsumeAsync("testqueue", true, "testconsumer", asyncListener);
+ await _doneEvent.WaitAsync(TimeSpan.FromSeconds(5));
_testOutputHelper.WriteLine("Received message");
- await subscriber.BasicCancelAsync(consumerTag);
+ await consumeChannel.BasicCancelAsync(consumerTag);
}
- private OAuth2ClientCredentialsProvider GetCredentialsProvider(OAuth2Options opts)
+ private OAuth2ClientCredentialsProvider GetCredentialsProvider(OAuth2OptionsBase opts)
{
_testOutputHelper.WriteLine("OAuth2Client ");
_testOutputHelper.WriteLine($"- ClientId: {opts.ClientId}");
@@ -245,10 +285,5 @@ private Task AsyncListener_Received(object sender, BasicDeliverEventArgs @event)
_doneEvent.Release();
return Task.CompletedTask;
}
-
- private static ICredentialsRefresher GetCredentialsRefresher()
- {
- return new TimerBasedCredentialRefresher();
- }
}
}
diff --git a/projects/Test/OAuth2/TestOAuth2Client.cs b/projects/Test/OAuth2/TestOAuth2Client.cs
index 3633b6fab1..6b6e50ced9 100644
--- a/projects/Test/OAuth2/TestOAuth2Client.cs
+++ b/projects/Test/OAuth2/TestOAuth2Client.cs
@@ -32,6 +32,7 @@
using System;
using System.Net.Http;
using System.Text.Json;
+using System.Threading.Tasks;
using RabbitMQ.Client.OAuth2;
using WireMock.RequestBuilders;
using WireMock.ResponseBuilders;
@@ -51,96 +52,63 @@ public class TestOAuth2Client
public TestOAuth2Client()
{
_oauthServer = WireMockServer.Start();
-
- _client = new OAuth2ClientBuilder(_client_id, _client_secret, new System.Uri(_oauthServer.Url + "/token")).Build();
- }
-
- private void expectTokenRequest(RequestFormMatcher expectedRequestBody, JsonToken expectedResponse)
- {
- _oauthServer
- .Given(
- Request.Create()
- .WithPath("/token")
- .WithBody(expectedRequestBody.Matcher())
- .UsingPost()
- )
- .RespondWith(
- Response.Create()
- .WithStatusCode(200)
- .WithHeader("Content-Type", "application/json;charset=UTF-8")
- .WithBody(JsonSerializer.Serialize(expectedResponse))
- );
+ var uri = new Uri(_oauthServer.Url + "/token");
+ _client = new OAuth2ClientBuilder(_client_id, _client_secret, uri).Build();
}
[Fact]
- public void TestRequestToken()
+ public async Task TestRequestToken()
{
JsonToken expectedJsonToken = new JsonToken("the_access_token", "the_refresh_token", TimeSpan.FromSeconds(10));
- expectTokenRequest(new RequestFormMatcher()
+ ExpectTokenRequest(new RequestFormMatcher()
.WithParam("client_id", _client_id)
.WithParam("client_secret", _client_secret)
.WithParam("grant_type", "client_credentials"),
expectedJsonToken);
- IToken token = _client.RequestToken();
+ IToken token = await _client.RequestTokenAsync();
Assert.NotNull(token);
- Assert.Equal(expectedJsonToken.access_token, token.AccessToken);
- Assert.Equal(expectedJsonToken.refresh_token, token.RefreshToken);
- Assert.Equal(TimeSpan.FromSeconds(expectedJsonToken.expires_in), token.ExpiresIn);
- }
-
- private void expectTokenRefresh(JsonToken expectedResponse)
- {
- _oauthServer
- .Given(
- Request.Create()
- .WithPath("/token")
- .WithParam("client_id", _client_id)
- .WithParam("client_secret", _client_secret)
- .WithParam("grant_type", "refresh_token")
- .WithParam("refresh_token", expectedResponse.refresh_token)
- .WithHeader("content_type", "application/x-www-form-urlencoded")
- .UsingPost()
- )
- .RespondWith(
- Response.Create()
- .WithStatusCode(200)
- .WithHeader("Content-Type", "application/json;charset=UTF-8")
- .WithBody(JsonSerializer.Serialize(expectedResponse))
- );
+ Assert.Equal(expectedJsonToken.AccessToken, token.AccessToken);
+ Assert.Equal(expectedJsonToken.RefreshToken, token.RefreshToken);
+ Assert.Equal(TimeSpan.FromSeconds(expectedJsonToken.ExpiresIn), token.ExpiresIn);
}
[Fact]
- public void TestRefreshToken()
+ public async Task TestRefreshToken()
{
- JsonToken expectedJsonToken = new JsonToken("the_access_token", "the_refresh_token", TimeSpan.FromSeconds(10));
- expectTokenRequest(new RequestFormMatcher()
+ const string accessToken0 = "the_access_token";
+ const string accessToken1 = "the_access_token_2";
+ const string refreshToken = "the_refresh_token";
+
+ JsonToken expectedJsonToken = new JsonToken(accessToken0, refreshToken, TimeSpan.FromSeconds(10));
+
+ ExpectTokenRequest(new RequestFormMatcher()
.WithParam("client_id", _client_id)
.WithParam("client_secret", _client_secret)
.WithParam("grant_type", "client_credentials"),
expectedJsonToken);
- IToken token = _client.RequestToken();
+ IToken token = await _client.RequestTokenAsync();
_oauthServer.Reset();
- expectedJsonToken = new JsonToken("the_access_token2", "the_refresh_token", TimeSpan.FromSeconds(20));
- expectTokenRequest(new RequestFormMatcher()
+ JsonToken responseJsonToken = new JsonToken(accessToken1, refreshToken, TimeSpan.FromSeconds(20));
+ ExpectTokenRefresh(new RequestFormMatcher()
.WithParam("client_id", _client_id)
.WithParam("client_secret", _client_secret)
.WithParam("grant_type", "refresh_token")
- .WithParam("refresh_token", "the_refresh_token"),
- expectedJsonToken);
+ .WithParam("refresh_token", refreshToken),
+ responseJsonToken);
- IToken refreshedToken = _client.RefreshToken(token);
- Assert.False(refreshedToken == token);
+ IToken refreshedToken = await _client.RefreshTokenAsync(token);
Assert.NotNull(refreshedToken);
- Assert.Equal(expectedJsonToken.access_token, refreshedToken.AccessToken);
- Assert.Equal(expectedJsonToken.refresh_token, refreshedToken.RefreshToken);
- Assert.Equal(TimeSpan.FromSeconds(expectedJsonToken.expires_in), refreshedToken.ExpiresIn);
+ Assert.False(Object.ReferenceEquals(refreshedToken, token));
+ Assert.Equal(responseJsonToken.AccessToken, refreshedToken.AccessToken);
+ Assert.Equal(responseJsonToken.RefreshToken, refreshedToken.RefreshToken);
+ Assert.Equal(TimeSpan.FromSeconds(responseJsonToken.ExpiresIn), refreshedToken.ExpiresIn);
}
[Fact]
- public void TestInvalidCredentials()
+ public async Task TestInvalidCredentials()
{
_oauthServer
.Given(
@@ -159,10 +127,46 @@ public void TestInvalidCredentials()
try
{
- IToken token = _client.RequestToken();
+ IToken token = await _client.RequestTokenAsync();
Assert.Fail("Should have thrown Exception");
}
- catch (HttpRequestException) { }
+ catch (HttpRequestException)
+ {
+ }
+ }
+
+ private void ExpectTokenRequest(RequestFormMatcher expectedRequestBody, JsonToken response)
+ {
+ _oauthServer
+ .Given(
+ Request.Create()
+ .WithPath("/token")
+ .WithBody(expectedRequestBody.Matcher())
+ .UsingPost()
+ )
+ .RespondWith(
+ Response.Create()
+ .WithStatusCode(200)
+ .WithHeader("Content-Type", "application/json;charset=UTF-8")
+ .WithBody(JsonSerializer.Serialize(response))
+ );
+ }
+
+ private void ExpectTokenRefresh(RequestFormMatcher expectedRequestBody, JsonToken expectedResponse)
+ {
+ _oauthServer
+ .Given(
+ Request.Create()
+ .WithPath("/token")
+ .WithBody(expectedRequestBody.Matcher())
+ .UsingPost()
+ )
+ .RespondWith(
+ Response.Create()
+ .WithStatusCode(200)
+ .WithHeader("Content-Type", "application/json;charset=UTF-8")
+ .WithBody(JsonSerializer.Serialize(expectedResponse))
+ );
}
}
}
diff --git a/projects/Test/OAuth2/TestOAuth2ClientCredentialsProvider.cs b/projects/Test/OAuth2/TestOAuth2ClientCredentialsProvider.cs
index 6c2604f960..98ac2acf7f 100644
--- a/projects/Test/OAuth2/TestOAuth2ClientCredentialsProvider.cs
+++ b/projects/Test/OAuth2/TestOAuth2ClientCredentialsProvider.cs
@@ -30,65 +30,14 @@
//---------------------------------------------------------------------------
using System;
-using System.Diagnostics;
-using System.Threading;
+using System.Threading.Tasks;
+using RabbitMQ.Client;
using RabbitMQ.Client.OAuth2;
using Xunit;
using Xunit.Abstractions;
namespace OAuth2Test
{
- public class MockIOAuth2Client : IOAuth2Client
- {
- private readonly ITestOutputHelper _testOutputHelper;
- private IToken _refreshToken;
- private IToken _requestToken;
-
- public MockIOAuth2Client(ITestOutputHelper testOutputHelper)
- {
- _testOutputHelper = testOutputHelper;
- }
-
- public IToken RefreshTokenValue
- {
- get { return _refreshToken; }
- set
- {
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
- _refreshToken = value;
- }
- }
-
- public IToken RequestTokenValue
- {
- get { return _requestToken; }
- set
- {
- if (value == null)
- {
- throw new ArgumentNullException(nameof(value));
- }
-
- _requestToken = value;
- }
- }
-
- public IToken RefreshToken(IToken initialToken)
- {
- Debug.Assert(Object.ReferenceEquals(_requestToken, initialToken));
- return _refreshToken;
- }
-
- public IToken RequestToken()
- {
- return _requestToken;
- }
- }
-
public class TestOAuth2CredentialsProvider
{
private readonly ITestOutputHelper _testOutputHelper;
@@ -102,42 +51,45 @@ public TestOAuth2CredentialsProvider(ITestOutputHelper testOutputHelper)
public void ShouldHaveAName()
{
const string name = "aName";
- IOAuth2Client oAuth2Client = new MockIOAuth2Client(_testOutputHelper);
+ IOAuth2Client oAuth2Client = new MockOAuth2Client(_testOutputHelper);
var provider = new OAuth2ClientCredentialsProvider(name, oAuth2Client);
-
Assert.Equal(name, provider.Name);
}
[Fact]
- public void ShouldRequestTokenWhenAskToRefresh()
+ public async Task ShouldRequestTokenWhenAskToRefresh()
{
const string newTokenValue = "the_access_token";
IToken newToken = NewToken(newTokenValue, TimeSpan.FromSeconds(60));
- var oAuth2Client = new MockIOAuth2Client(_testOutputHelper);
+ var oAuth2Client = new MockOAuth2Client(_testOutputHelper);
oAuth2Client.RequestTokenValue = newToken;
var provider = new OAuth2ClientCredentialsProvider(nameof(ShouldRequestTokenWhenAskToRefresh), oAuth2Client);
- provider.Refresh();
+ Credentials credentials = await provider.GetCredentialsAsync();
- Assert.Equal(newTokenValue, provider.Password);
+ Assert.Equal(newTokenValue, credentials.Password);
}
[Fact]
- public void ShouldRequestTokenWhenGettingPasswordOrValidUntilForFirstTimeAccess()
+ public async Task ShouldRequestTokenWhenGettingPasswordOrValidUntilForFirstTimeAccess()
{
const string accessToken = "the_access_token";
const string refreshToken = "the_refresh_token";
IToken firstToken = NewToken(accessToken, refreshToken, TimeSpan.FromSeconds(1));
- var oAuth2Client = new MockIOAuth2Client(_testOutputHelper);
+
+ var oAuth2Client = new MockOAuth2Client(_testOutputHelper);
oAuth2Client.RequestTokenValue = firstToken;
var provider = new OAuth2ClientCredentialsProvider(nameof(ShouldRequestTokenWhenGettingPasswordOrValidUntilForFirstTimeAccess), oAuth2Client);
- Assert.Equal(firstToken.AccessToken, provider.Password);
- Assert.Equal(firstToken.ExpiresIn, provider.ValidUntil.Value);
+ Credentials credentials = await provider.GetCredentialsAsync();
+
+ Assert.Equal(firstToken.AccessToken, credentials.Password);
+ Assert.NotNull(credentials.ValidUntil);
+ Assert.Equal(firstToken.ExpiresIn, credentials.ValidUntil.Value);
}
[Fact]
- public void ShouldRefreshTokenUsingRefreshTokenWhenAvailable()
+ public async Task ShouldRefreshTokenUsingRefreshTokenWhenAvailable()
{
const string accessToken = "the_access_token";
const string refreshToken = "the_refresh_token";
@@ -146,44 +98,62 @@ public void ShouldRefreshTokenUsingRefreshTokenWhenAvailable()
IToken firstToken = NewToken(accessToken, refreshToken, TimeSpan.FromSeconds(1));
IToken refreshedToken = NewToken(accessToken2, refreshToken2, TimeSpan.FromSeconds(60));
- var oAuth2Client = new MockIOAuth2Client(_testOutputHelper);
+
+ var oAuth2Client = new MockOAuth2Client(_testOutputHelper);
oAuth2Client.RequestTokenValue = firstToken;
- var provider = new OAuth2ClientCredentialsProvider(nameof(ShouldRequestTokenWhenGettingPasswordOrValidUntilForFirstTimeAccess), oAuth2Client);
- provider.Refresh();
+ var provider = new OAuth2ClientCredentialsProvider(nameof(ShouldRefreshTokenUsingRefreshTokenWhenAvailable), oAuth2Client);
+
+ Credentials credentials = await provider.GetCredentialsAsync();
- Assert.Equal(firstToken.AccessToken, provider.Password);
- Assert.Equal(firstToken.ExpiresIn, provider.ValidUntil.Value);
+ Assert.Equal(firstToken.AccessToken, credentials.Password);
+ Assert.NotNull(credentials.ValidUntil);
+ Assert.Equal(firstToken.ExpiresIn, credentials.ValidUntil.Value);
oAuth2Client.RefreshTokenValue = refreshedToken;
- provider.Refresh();
- Assert.Equal(refreshedToken.AccessToken, provider.Password);
- Assert.Equal(refreshedToken.ExpiresIn, provider.ValidUntil.Value);
+ while (false == firstToken.HasExpired)
+ {
+ await Task.Delay(100);
+ }
+
+ credentials = await provider.GetCredentialsAsync();
+
+ Assert.Equal(refreshedToken.AccessToken, credentials.Password);
+ Assert.NotNull(credentials.ValidUntil);
+ Assert.Equal(refreshedToken.ExpiresIn, credentials.ValidUntil.Value);
}
[Fact]
- public void ShouldRequestTokenWhenRefreshTokenNotAvailable()
+ public async Task ShouldRequestTokenWhenRefreshTokenNotAvailable()
{
const string accessToken = "the_access_token";
const string accessToken2 = "the_access_token_2";
- IToken firstToken = NewToken(accessToken, null, TimeSpan.FromSeconds(1));
- IToken secondToken = NewToken(accessToken2, null, TimeSpan.FromSeconds(60));
+ IToken firstToken = NewToken(accessToken, string.Empty, TimeSpan.FromSeconds(1));
+ IToken secondToken = NewToken(accessToken2, string.Empty, TimeSpan.FromSeconds(60));
- var oAuth2Client = new MockIOAuth2Client(_testOutputHelper);
+ var oAuth2Client = new MockOAuth2Client(_testOutputHelper);
oAuth2Client.RequestTokenValue = firstToken;
var provider = new OAuth2ClientCredentialsProvider(nameof(ShouldRequestTokenWhenRefreshTokenNotAvailable), oAuth2Client);
- provider.Refresh();
+ Credentials credentials = await provider.GetCredentialsAsync();
- Assert.Equal(firstToken.AccessToken, provider.Password);
- Assert.Equal(firstToken.ExpiresIn, provider.ValidUntil.Value);
+ Assert.Equal(firstToken.AccessToken, credentials.Password);
+ Assert.NotNull(credentials.ValidUntil);
+ Assert.Equal(firstToken.ExpiresIn, credentials.ValidUntil.Value);
oAuth2Client.RequestTokenValue = secondToken;
- provider.Refresh();
- Assert.Equal(secondToken.AccessToken, provider.Password);
- Assert.Equal(secondToken.ExpiresIn, provider.ValidUntil.Value);
+ while (false == firstToken.HasExpired)
+ {
+ await Task.Delay(100);
+ }
+
+ credentials = await provider.GetCredentialsAsync();
+
+ Assert.Equal(secondToken.AccessToken, credentials.Password);
+ Assert.NotNull(credentials.ValidUntil);
+ Assert.Equal(secondToken.ExpiresIn, credentials.ValidUntil.Value);
}
private static Token NewToken(string access_token, TimeSpan expiresIn)
diff --git a/projects/Test/OAuth2/keycloak/import/test-realm.json b/projects/Test/OAuth2/keycloak/import/test-realm.json
index df2b7594f8..d34986eaaa 100644
--- a/projects/Test/OAuth2/keycloak/import/test-realm.json
+++ b/projects/Test/OAuth2/keycloak/import/test-realm.json
@@ -508,7 +508,7 @@
"credentials" : [ ],
"disableableCredentialTypes" : [ ],
"requiredActions" : [ ],
- "realmRoles" : [ "default-roles-test", "rabbitmq-management" ],
+ "realmRoles" : [ "default-roles-test", "rabbitmq-management", "rabbitmq.tag:administrator" ],
"notBefore" : 0,
"groups" : [ ]
}, {
diff --git a/projects/Test/OAuth2/keycloak/rabbitmq.conf b/projects/Test/OAuth2/keycloak/rabbitmq.conf
index 207106a14f..ed2a692c3b 100644
--- a/projects/Test/OAuth2/keycloak/rabbitmq.conf
+++ b/projects/Test/OAuth2/keycloak/rabbitmq.conf
@@ -1,4 +1,7 @@
-auth_backends.1 = rabbit_auth_backend_oauth2
+log.console = true
+log.console.level = debug
+
+auth_backends.1 = oauth2
auth_oauth2.resource_server_id = rabbitmq
auth_oauth2.preferred_username_claims.1 = username
diff --git a/projects/Test/OAuth2/uaa/rabbitmq.conf b/projects/Test/OAuth2/uaa/rabbitmq.conf
index b0a6d5b0f4..5edca13cc7 100644
--- a/projects/Test/OAuth2/uaa/rabbitmq.conf
+++ b/projects/Test/OAuth2/uaa/rabbitmq.conf
@@ -1,4 +1,8 @@
-auth_backends.1 = rabbit_auth_backend_oauth2
+log.console = true
+log.console.level = debug
+
+auth_backends.1 = oauth2
+
management.oauth_enabled = true
management.oauth_client_id = rabbit_client_code
management.oauth_provider_url = http://localhost:8080/
diff --git a/projects/Test/OAuth2/uaa/uaa.yml b/projects/Test/OAuth2/uaa/uaa.yml
index 8772706f74..c3efb7633c 100644
--- a/projects/Test/OAuth2/uaa/uaa.yml
+++ b/projects/Test/OAuth2/uaa/uaa.yml
@@ -125,7 +125,7 @@ oauth:
id: mgt_api_client
secret: mgt_api_client
authorized-grant-types: client_credentials
- authorities: rabbitmq.tag:monitoring
+ authorities: rabbitmq.tag:administrator
rabbit_client_code:
id: rabbit_client_code
secret: rabbit_client_code
diff --git a/projects/Test/Unit/TestTimerBasedCredentialRefresher.cs b/projects/Test/Unit/TestTimerBasedCredentialRefresher.cs
deleted file mode 100644
index faed6f03f7..0000000000
--- a/projects/Test/Unit/TestTimerBasedCredentialRefresher.cs
+++ /dev/null
@@ -1,181 +0,0 @@
-// This source code is dual-licensed under the Apache License, version
-// 2.0, and the Mozilla Public License, version 2.0.
-//
-// The APL v2.0:
-//
-//---------------------------------------------------------------------------
-// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// https://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-//---------------------------------------------------------------------------
-//
-// The MPL v2.0:
-//
-//---------------------------------------------------------------------------
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at https://mozilla.org/MPL/2.0/.
-//
-// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
-//---------------------------------------------------------------------------
-
-using System;
-using System.Threading;
-using System.Threading.Tasks;
-using RabbitMQ.Client;
-using Xunit;
-using Xunit.Abstractions;
-
-namespace Test.Unit
-{
- public class MockCredentialsProvider : ICredentialsProvider
- {
- private readonly ITestOutputHelper _testOutputHelper;
- private readonly TimeSpan? _validUntil = TimeSpan.FromSeconds(1);
- private Exception _ex = null;
- private bool _refreshCalled = false;
-
- public MockCredentialsProvider(ITestOutputHelper testOutputHelper)
- {
- _testOutputHelper = testOutputHelper;
- }
-
- public MockCredentialsProvider(ITestOutputHelper testOutputHelper, TimeSpan validUntil)
- {
- _testOutputHelper = testOutputHelper;
- _validUntil = validUntil;
- }
-
- public bool RefreshCalled
- {
- get
- {
- return _refreshCalled;
- }
- }
-
- public string Name => this.GetType().Name;
-
- public string UserName => "guest";
-
- public string Password
- {
- get
- {
- if (_ex == null)
- {
- return "guest";
- }
- else
- {
- throw _ex;
- }
- }
- }
-
- public TimeSpan? ValidUntil => _validUntil;
-
- public void Refresh()
- {
- _refreshCalled = true;
- }
-
- public void PasswordThrows(Exception ex)
- {
- _ex = ex;
- }
- }
-
- public class TestTimerBasedCredentialsRefresher
- {
- private readonly ITestOutputHelper _testOutputHelper;
- private readonly TimerBasedCredentialRefresher _refresher = new TimerBasedCredentialRefresher();
-
- public TestTimerBasedCredentialsRefresher(ITestOutputHelper testOutputHelper)
- {
- _testOutputHelper = testOutputHelper;
- }
-
- [Fact]
- public void TestRegister()
- {
- Task cb(bool unused) => Task.CompletedTask;
- ICredentialsProvider credentialsProvider = new MockCredentialsProvider(_testOutputHelper);
-
- Assert.True(credentialsProvider == _refresher.Register(credentialsProvider, cb));
- Assert.True(_refresher.Unregister(credentialsProvider));
- }
-
- [Fact]
- public void TestDoNotRegisterWhenHasNoExpiry()
- {
- ICredentialsProvider credentialsProvider = new MockCredentialsProvider(_testOutputHelper, TimeSpan.Zero);
- Task cb(bool unused) => Task.CompletedTask;
-
- _refresher.Register(credentialsProvider, cb);
-
- Assert.False(_refresher.Unregister(credentialsProvider));
- }
-
- [Fact]
- public async Task TestRefreshToken()
- {
- var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
- {
- using (CancellationTokenRegistration ctr = cts.Token.Register(() => tcs.TrySetCanceled()))
- {
- var credentialsProvider = new MockCredentialsProvider(_testOutputHelper, TimeSpan.FromSeconds(1));
-
- Task cb(bool arg)
- {
- tcs.SetResult(arg);
- return Task.CompletedTask;
- }
-
- _refresher.Register(credentialsProvider, cb);
- Assert.True(await tcs.Task);
- Assert.True(credentialsProvider.RefreshCalled);
- Assert.True(_refresher.Unregister(credentialsProvider));
- }
- }
- }
-
- [Fact]
- public async Task TestRefreshTokenFailed()
- {
- var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
- using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)))
- {
- using (CancellationTokenRegistration ctr = cts.Token.Register(() => tcs.TrySetCanceled()))
- {
- var credentialsProvider = new MockCredentialsProvider(_testOutputHelper, TimeSpan.FromSeconds(1));
-
- Task cb(bool arg)
- {
- tcs.SetResult(arg);
- return Task.CompletedTask;
- }
-
- var ex = new Exception();
- credentialsProvider.PasswordThrows(ex);
-
- _refresher.Register(credentialsProvider, cb);
- Assert.False(await tcs.Task);
- Assert.True(credentialsProvider.RefreshCalled);
- Assert.True(_refresher.Unregister(credentialsProvider));
- }
- }
- }
- }
-}