Skip to content

Commit 1e63390

Browse files
committed
Refactor credential refresh
Fixes #1639 * Remove all traces of credential refresh from `RabbitMQ.Client` * Add `CredentialsRefresher`, which does not use timers, and calls refresh based on `ValidUntil` *
1 parent 5aca35a commit 1e63390

21 files changed

+474
-482
lines changed

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

Lines changed: 7 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -30,47 +30,29 @@
3030
//---------------------------------------------------------------------------
3131

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

34-
namespace RabbitMQ.Client
36+
namespace RabbitMQ.Client.OAuth2
3537
{
3638
public class BasicCredentialsProvider : ICredentialsProvider
3739
{
3840
private readonly string _name;
3941
private readonly string _userName;
4042
private readonly string _password;
4143

42-
public BasicCredentialsProvider(string? name, string userName, string password)
44+
public BasicCredentialsProvider(string name, string userName, string password)
4345
{
4446
_name = name ?? string.Empty;
4547
_userName = userName ?? throw new ArgumentNullException(nameof(userName));
4648
_password = password ?? throw new ArgumentNullException(nameof(password));
4749
}
4850

49-
public string Name
50-
{
51-
get { return _name; }
52-
}
53-
54-
public string UserName
55-
{
56-
get { return _userName; }
57-
}
58-
59-
public string Password
60-
{
61-
get { return _password; }
62-
}
63-
64-
public Nullable<TimeSpan> ValidUntil
65-
{
66-
get
67-
{
68-
return null;
69-
}
70-
}
51+
public string Name => _name;
7152

72-
public void Refresh()
53+
public Task<Credentials> GetCredentialsAsync(CancellationToken cancellationToken = default)
7354
{
55+
return Task.FromResult(new Credentials(_name, _userName, _password, null));
7456
}
7557
}
7658
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// This source code is dual-licensed under the Apache License, version
2+
// 2.0, and the Mozilla Public License, version 2.0.
3+
//
4+
// The APL v2.0:
5+
//
6+
//---------------------------------------------------------------------------
7+
// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
8+
//
9+
// Licensed under the Apache License, Version 2.0 (the "License");
10+
// you may not use this file except in compliance with the License.
11+
// You may obtain a copy of the License at
12+
//
13+
// https://www.apache.org/licenses/LICENSE-2.0
14+
//
15+
// Unless required by applicable law or agreed to in writing, software
16+
// distributed under the License is distributed on an "AS IS" BASIS,
17+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18+
// See the License for the specific language governing permissions and
19+
// limitations under the License.
20+
//---------------------------------------------------------------------------
21+
//
22+
// The MPL v2.0:
23+
//
24+
//---------------------------------------------------------------------------
25+
// This Source Code Form is subject to the terms of the Mozilla Public
26+
// License, v. 2.0. If a copy of the MPL was not distributed with this
27+
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
28+
//
29+
// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
30+
//---------------------------------------------------------------------------
31+
32+
using System;
33+
using System.Threading;
34+
using System.Threading.Tasks;
35+
36+
// TODO rabbitmq-dotnet-client-1639
37+
/*
38+
private async Task NotifyCredentialRefreshedAsync(bool succesfully)
39+
{
40+
if (succesfully)
41+
{
42+
using var cts = new CancellationTokenSource(InternalConstants.DefaultConnectionCloseTimeout);
43+
await UpdateSecretAsync(_config.CredentialsProvider.Password, "Token refresh", cts.Token)
44+
.ConfigureAwait(false);
45+
}
46+
}
47+
*/
48+
49+
namespace RabbitMQ.Client.OAuth2
50+
{
51+
public delegate Task NotifyCredentialsRefreshedAsync(Credentials? credentials,
52+
Exception? exception = null,
53+
CancellationToken cancellationToken = default);
54+
55+
public class CredentialsRefresher : IDisposable
56+
{
57+
private readonly ICredentialsProvider _credentialsProvider;
58+
private readonly NotifyCredentialsRefreshedAsync _onRefreshed;
59+
60+
private readonly CancellationTokenSource _internalCts = new CancellationTokenSource();
61+
private readonly CancellationTokenSource _linkedCts;
62+
63+
private readonly Task _refreshTask;
64+
65+
private Credentials? _credentials;
66+
private bool _disposedValue = false;
67+
68+
public CredentialsRefresher(ICredentialsProvider credentialsProvider,
69+
NotifyCredentialsRefreshedAsync onRefreshed,
70+
CancellationToken cancellationToken)
71+
{
72+
if (credentialsProvider is null)
73+
{
74+
throw new ArgumentNullException(nameof(credentialsProvider));
75+
}
76+
77+
if (onRefreshed is null)
78+
{
79+
throw new ArgumentNullException(nameof(onRefreshed));
80+
}
81+
82+
_linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_internalCts.Token, cancellationToken);
83+
84+
_credentialsProvider = credentialsProvider;
85+
_onRefreshed = onRefreshed;
86+
87+
_refreshTask = Task.Run(RefreshLoopAsync, _linkedCts.Token);
88+
89+
TimerBasedCredentialRefresherEventSource.Log.Registered(_credentialsProvider.Name);
90+
}
91+
92+
private async Task RefreshLoopAsync()
93+
{
94+
while (false == _linkedCts.IsCancellationRequested)
95+
{
96+
try
97+
{
98+
_credentials = await _credentialsProvider.GetCredentialsAsync(_linkedCts.Token)
99+
.ConfigureAwait(false);
100+
101+
if (_linkedCts.IsCancellationRequested)
102+
{
103+
break;
104+
}
105+
106+
await _onRefreshed(_credentials, null, _linkedCts.Token)
107+
.ConfigureAwait(false);
108+
}
109+
catch (OperationCanceledException)
110+
{
111+
return;
112+
}
113+
catch (Exception ex)
114+
{
115+
await _onRefreshed(null, ex, _linkedCts.Token)
116+
.ConfigureAwait(false);
117+
}
118+
119+
TimeSpan delaySpan = TimeSpan.FromMinutes(5); // TODO
120+
if (_credentials != null && _credentials.ValidUntil.HasValue)
121+
{
122+
delaySpan = _credentials.ValidUntil.Value;
123+
}
124+
125+
await Task.Delay(delaySpan, _linkedCts.Token)
126+
.ConfigureAwait(false);
127+
}
128+
}
129+
130+
public void Dispose()
131+
{
132+
Dispose(disposing: true);
133+
GC.SuppressFinalize(this);
134+
}
135+
136+
protected virtual void Dispose(bool disposing)
137+
{
138+
if (!_disposedValue)
139+
{
140+
if (disposing)
141+
{
142+
_internalCts.Cancel();
143+
_refreshTask.Wait(); // TODO do we care?
144+
_internalCts.Dispose();
145+
_linkedCts.Dispose();
146+
}
147+
148+
_disposedValue = true;
149+
}
150+
}
151+
}
152+
}

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

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,25 +29,41 @@
2929
// Copyright (c) 2007-2024 Broadcom. All Rights Reserved.
3030
//---------------------------------------------------------------------------
3131

32-
namespace RabbitMQ.Client
32+
using System;
33+
using System.Threading;
34+
using System.Threading.Tasks;
35+
36+
namespace RabbitMQ.Client.OAuth2
3337
{
3438
public interface ICredentialsProvider
3539
{
3640
string Name { get; }
37-
string UserName { get; }
38-
string Password { get; }
41+
Task<Credentials> GetCredentialsAsync(CancellationToken cancellationToken = default);
42+
}
43+
44+
public class Credentials
45+
{
46+
private readonly string _name;
47+
private readonly string _userName;
48+
private readonly string _password;
49+
private readonly TimeSpan? _validUntil;
50+
51+
public Credentials(string name, string userName, string password, TimeSpan? validUntil)
52+
{
53+
_name = name;
54+
_userName = userName;
55+
_password = password;
56+
_validUntil = validUntil;
57+
}
58+
59+
public string Name => _name;
60+
public string UserName => _userName;
61+
public string Password => _password;
3962

4063
/// <summary>
4164
/// If credentials have an expiry time this property returns the interval.
4265
/// Otherwise, it returns null.
4366
/// </summary>
44-
System.TimeSpan? ValidUntil { get; }
45-
46-
/// <summary>
47-
/// Before the credentials are available, be it Username, Password or ValidUntil,
48-
/// the credentials must be obtained by calling this method.
49-
/// And to keep it up to date, this method must be called before the ValidUntil interval.
50-
/// </summary>
51-
void Refresh();
67+
public TimeSpan? ValidUntil => _validUntil;
5268
}
5369
}

0 commit comments

Comments
 (0)