Skip to content

Commit

Permalink
Merge branch 'dev' into tefa/update-swagger-to-20221101
Browse files Browse the repository at this point in the history
  • Loading branch information
terencefan authored Jun 24, 2024
2 parents e214822 + 0ee1765 commit d1d9ad3
Show file tree
Hide file tree
Showing 56 changed files with 836 additions and 519 deletions.
5 changes: 5 additions & 0 deletions .azure/pipelines/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ extends:
inputs:
version: 7.x
performMultiLevelLookup: true
- task: UseDotNet@2
displayName: Add 8.x
inputs:
version: 8.x
performMultiLevelLookup: true
- ${{ if or( eq(parameters.isFinalBuild, false), eq(parameters.releaseSDKCore, true)) }}:
- task: DotNetCoreCLI@2
displayName: dotnet build AzureSignalR.sln
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/osx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: macOS-latest
strategy:
matrix:
dotnet-version: [ '7.0.x', '6.0.x' ]
dotnet-version: [ '8.0.x', '7.0.x', '6.0.x' ]
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
Expand Down Expand Up @@ -43,7 +43,7 @@ jobs:
run: dotnet --version
- name: Build with dotnet
run: |
if [ "${{ matrix.dotnet-version }}" = "7.0.x" ]; then
if [ "${{ matrix.dotnet-version }}" != "6.0.x" ]; then
dotnet sln AzureSignalR.sln remove test/Microsoft.Azure.SignalR.Protocols.Tests/Microsoft.Azure.SignalR.Protocols.Tests.csproj
dotnet sln AzureSignalR.sln remove test/Microsoft.Azure.SignalR.Emulator.Tests/Microsoft.Azure.SignalR.Emulator.Tests.csproj
dotnet sln AzureSignalR.sln remove test/Microsoft.Azure.SignalR.Management.Tests/Microsoft.Azure.SignalR.Management.Tests.csproj
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
dotnet-version: [ '7.0.x', '6.0.x' ]
dotnet-version: [ '8.0.x', '7.0.x', '6.0.x' ]
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
runs-on: [windows-latest]
strategy:
matrix:
dotnet-version: [ '7.0.x', '6.0.x' ]
dotnet-version: [ '8.0.x', '7.0.x', '6.0.x' ]
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,9 @@ public string GetServerEndpoint(string hubName)

public IAccessTokenProvider GetServerAccessTokenProvider(string hubName, string serverId)
{
if (_accessKey is AadAccessKey aadAccessKey)
if (_accessKey is AccessKeyForMicrosoftEntra key)
{
return new AadTokenProvider(aadAccessKey);
return new MicrosoftEntraTokenProvider(key);
}
else if (_accessKey is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@

namespace Microsoft.Azure.SignalR
{
internal class AadTokenProvider : IAccessTokenProvider
internal class MicrosoftEntraTokenProvider : IAccessTokenProvider
{
private readonly AadAccessKey _accessKey;
private readonly AccessKeyForMicrosoftEntra _accessKey;

public AadTokenProvider(AadAccessKey accessKey)
public MicrosoftEntraTokenProvider(AccessKeyForMicrosoftEntra accessKey)
{
_accessKey = accessKey ?? throw new ArgumentNullException(nameof(accessKey));
}

public Task<string> ProvideAsync() => _accessKey.GenerateAadTokenAsync();
public Task<string> ProvideAsync() => _accessKey.GetMicrosoftEntraTokenAsync();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,43 @@

namespace Microsoft.Azure.SignalR
{
internal class AadAccessKey : AccessKey
internal class AccessKeyForMicrosoftEntra : AccessKey
{
internal const int AuthorizeIntervalInMinute = 55;
internal static readonly TimeSpan GetAccessKeyTimeout = TimeSpan.FromSeconds(100);

internal const int AuthorizeMaxRetryTimes = 3;
private const int GetAccessKeyIntervalInMinute = 55;

internal const int AuthorizeRetryIntervalInSec = 3;
private const int GetAccessKeyMaxRetryTimes = 3;

internal const int GetTokenMaxRetryTimes = 3;

internal static readonly TimeSpan AuthorizeTimeout = TimeSpan.FromSeconds(100);
private const int GetMicrosoftEntraTokenMaxRetryTimes = 3;

private const string DefaultScope = "https://signalr.azure.com/.default";

private static readonly TimeSpan AuthorizeInterval = TimeSpan.FromMinutes(AuthorizeIntervalInMinute);

private static readonly TokenRequestContext DefaultRequestContext = new TokenRequestContext(new string[] { DefaultScope });

private static readonly TimeSpan AuthorizeIntervalWhenFailed = TimeSpan.FromMinutes(5);
private static readonly TimeSpan GetAccessKeyInterval = TimeSpan.FromMinutes(GetAccessKeyIntervalInMinute);

private static readonly TimeSpan AuthorizeRetryInterval = TimeSpan.FromSeconds(AuthorizeRetryIntervalInSec);
private static readonly TimeSpan GetAccessKeyIntervalWhenUnauthorized = TimeSpan.FromMinutes(5);

private static readonly TimeSpan GetAccessKeyRetryInterval = TimeSpan.FromSeconds(3);

private readonly TaskCompletionSource<object> _initializedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);

private volatile bool _isAuthorized = false;

private Exception _lastException;

private DateTime _lastUpdatedTime = DateTime.MinValue;

public bool Authorized
public bool IsAuthorized
{
get => _isAuthorized;
private set
{
if (value)
{
_lastException = null;
}
_lastUpdatedTime = DateTime.UtcNow;
_isAuthorized = value;
_initializedTcs.TrySetResult(null);
Expand All @@ -58,23 +62,23 @@ private set

public TokenCredential TokenCredential { get; }

internal string AuthorizeUrl { get; }
internal string GetAccessKeyUrl { get; }

internal bool HasExpired => DateTime.UtcNow - _lastUpdatedTime > TimeSpan.FromMinutes(AuthorizeIntervalInMinute * 2);
internal bool HasExpired => DateTime.UtcNow - _lastUpdatedTime > TimeSpan.FromMinutes(GetAccessKeyIntervalInMinute * 2);

private Task<object> InitializedTask => _initializedTcs.Task;

public AadAccessKey(Uri endpoint, TokenCredential credential, Uri serverEndpoint = null) : base(endpoint)
public AccessKeyForMicrosoftEntra(Uri endpoint, TokenCredential credential, Uri serverEndpoint = null) : base(endpoint)
{
var authorizeUri = (serverEndpoint ?? endpoint).Append("/api/v1/auth/accessKey");
AuthorizeUrl = authorizeUri.AbsoluteUri;
GetAccessKeyUrl = authorizeUri.AbsoluteUri;
TokenCredential = credential;
}

public virtual async Task<string> GenerateAadTokenAsync(CancellationToken ctoken = default)
public virtual async Task<string> GetMicrosoftEntraTokenAsync(CancellationToken ctoken = default)
{
Exception latest = null;
for (var i = 0; i < GetTokenMaxRetryTimes; i++)
for (var i = 0; i < GetMicrosoftEntraTokenMaxRetryTimes; i++)
{
try
{
Expand All @@ -101,14 +105,9 @@ public override async Task<string> GenerateAccessTokenAsync(
if (task == InitializedTask || InitializedTask.IsCompleted)
{
await task;
if (Authorized)
{
return await base.GenerateAccessTokenAsync(audience, claims, lifetime, algorithm);
}
else
{
throw new AzureSignalRAccessTokenNotAuthorizedException("The given AzureAD identity don't have the permission to generate access token.");
}
return IsAuthorized
? await base.GenerateAccessTokenAsync(audience, claims, lifetime, algorithm)
: throw new AzureSignalRAccessTokenNotAuthorizedException(TokenCredential.GetType().Name, _lastException);
}
else
{
Expand All @@ -119,47 +118,42 @@ public override async Task<string> GenerateAccessTokenAsync(
internal void UpdateAccessKey(string kid, string accessKey)
{
Key = new Tuple<string, string>(kid, accessKey);
Authorized = true;
IsAuthorized = true;
}

internal async Task UpdateAccessKeyAsync(CancellationToken ctoken = default)
{
var delta = DateTime.UtcNow - _lastUpdatedTime;
if (Authorized && delta < AuthorizeInterval)
if (IsAuthorized && delta < GetAccessKeyInterval)
{
return;
}
else if (!Authorized && delta < AuthorizeIntervalWhenFailed)
else if (!IsAuthorized && delta < GetAccessKeyIntervalWhenUnauthorized)
{
return;
}
await AuthorizeWithRetryAsync(ctoken);
}

private async Task AuthorizeWithRetryAsync(CancellationToken ctoken = default)
{
Exception latest = null;
for (var i = 0; i < AuthorizeMaxRetryTimes; i++)
for (var i = 0; i < GetAccessKeyMaxRetryTimes; i++)
{
var source = new CancellationTokenSource(AuthorizeTimeout);
var source = new CancellationTokenSource(GetAccessKeyTimeout);
var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(source.Token, ctoken);
try
{
var token = await GenerateAadTokenAsync(linkedSource.Token);
await AuthorizeWithTokenAsync(token, linkedSource.Token);
var token = await GetMicrosoftEntraTokenAsync(linkedSource.Token);
await GetAccessKeyInternalAsync(token, linkedSource.Token);
return;
}
catch (OperationCanceledException e)
{
latest = e;
_lastException = e;
break;
}
catch (Exception e)
{
latest = e;
_lastException = e;
try
{
await Task.Delay(AuthorizeRetryInterval, ctoken);
await Task.Delay(GetAccessKeyRetryInterval, ctoken);
}
catch (OperationCanceledException)
{
Expand All @@ -168,13 +162,12 @@ private async Task AuthorizeWithRetryAsync(CancellationToken ctoken = default)
}
}

Authorized = false;
throw latest;
IsAuthorized = false;
}

private async Task AuthorizeWithTokenAsync(string accessToken, CancellationToken ctoken = default)
private async Task GetAccessKeyInternalAsync(string accessToken, CancellationToken ctoken = default)
{
var api = new RestApiEndpoint(AuthorizeUrl, accessToken);
var api = new RestApiEndpoint(GetAccessKeyUrl, accessToken);

await new RestClient().SendAsync(
api,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,24 @@

namespace Microsoft.Azure.SignalR
{
internal partial class AccessKeySynchronizer : IAccessKeySynchronizer, IDisposable
internal sealed class AccessKeySynchronizer : IAccessKeySynchronizer, IDisposable
{
private readonly ConcurrentDictionary<ServiceEndpoint, object> _endpoints = new ConcurrentDictionary<ServiceEndpoint, object>(ReferenceEqualityComparer.Instance);

private readonly ILoggerFactory _factory;

private readonly TimerAwaitable _timer = new TimerAwaitable(TimeSpan.Zero, TimeSpan.FromMinutes(1));

public AccessKeySynchronizer(
ILoggerFactory loggerFactory
) : this(loggerFactory, true)
internal IEnumerable<AccessKeyForMicrosoftEntra> AccessKeyForMicrosoftEntraList => _endpoints.Select(e => e.Key.AccessKey).OfType<AccessKeyForMicrosoftEntra>();

public AccessKeySynchronizer(ILoggerFactory loggerFactory) : this(loggerFactory, true)
{
}

/// <summary>
/// For test only.
/// Test only.
/// </summary>
internal AccessKeySynchronizer(
ILoggerFactory loggerFactory,
bool start
)
internal AccessKeySynchronizer(ILoggerFactory loggerFactory, bool start)
{
if (start)
{
Expand All @@ -42,9 +39,9 @@ bool start

public void AddServiceEndpoint(ServiceEndpoint endpoint)
{
if (endpoint.AccessKey is AadAccessKey aadKey)
if (endpoint.AccessKey is AccessKeyForMicrosoftEntra key)
{
_ = UpdateAccessKeyAsync(aadKey);
_ = key.UpdateAccessKeyAsync();
}
_endpoints.TryAdd(endpoint, null);
}
Expand All @@ -64,8 +61,6 @@ public void UpdateServiceEndpoints(IEnumerable<ServiceEndpoint> endpoints)

internal int ServiceEndpointsCount() => _endpoints.Count;

internal IEnumerable<AadAccessKey> FilterAadAccessKeys() => _endpoints.Select(e => e.Key.AccessKey).OfType<AadAccessKey>();

private async Task UpdateAccessKeyAsync()
{
using (_timer)
Expand All @@ -74,28 +69,14 @@ private async Task UpdateAccessKeyAsync()

while (await _timer)
{
foreach (var key in FilterAadAccessKeys())
foreach (var key in AccessKeyForMicrosoftEntraList)
{
_ = UpdateAccessKeyAsync(key);
_ = key.UpdateAccessKeyAsync();
}
}
}
}

private async Task UpdateAccessKeyAsync(AadAccessKey key)
{
var logger = _factory.CreateLogger<AadAccessKey>();
try
{
await key.UpdateAccessKeyAsync();
Log.SucceedToAuthorizeAccessKey(logger, key.AuthorizeUrl);
}
catch (Exception e)
{
Log.FailedToAuthorizeAccessKey(logger, key.AuthorizeUrl, e);
}
}

private sealed class ReferenceEqualityComparer : IEqualityComparer<ServiceEndpoint>
{
internal static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ internal AccessKey AccessKey
{
lock (_lock)
{
_accessKey ??= new AadAccessKey(_serviceEndpoint, _tokenCredential, ServerEndpoint);
_accessKey ??= new AccessKeyForMicrosoftEntra(_serviceEndpoint, _tokenCredential, ServerEndpoint);
}
}
return _accessKey;
Expand Down
Loading

0 comments on commit d1d9ad3

Please sign in to comment.