diff --git a/src/Microsoft.Azure.SignalR.AspNet/Middleware/NegotiateMiddleware.cs b/src/Microsoft.Azure.SignalR.AspNet/Middleware/NegotiateMiddleware.cs index 11363c6b1..e3a467959 100644 --- a/src/Microsoft.Azure.SignalR.AspNet/Middleware/NegotiateMiddleware.cs +++ b/src/Microsoft.Azure.SignalR.AspNet/Middleware/NegotiateMiddleware.cs @@ -184,7 +184,7 @@ private Task ProcessNegotiationRequest(IOwinContext owinContext, HostContext con { return GenerateClientAccessTokenAsync(provider, context, url, claims); } - catch (AzureSignalRAccessTokenNotAuthorizedException e) + catch (AzureSignalRAccessKeyNotAvailableException e) { Log.NegotiateFailed(_logger, e.Message); context.Response.StatusCode = 500; diff --git a/src/Microsoft.Azure.SignalR.Common/Auth/MicrosoftEntra/MicrosoftEntraAccessKey.Rest.cs b/src/Microsoft.Azure.SignalR.Common/Auth/MicrosoftEntra/MicrosoftEntraAccessKey.Rest.cs deleted file mode 100644 index c17098e2e..000000000 --- a/src/Microsoft.Azure.SignalR.Common/Auth/MicrosoftEntra/MicrosoftEntraAccessKey.Rest.cs +++ /dev/null @@ -1,117 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using System.Net; -using System.Net.Http; -using System.Net.Http.Headers; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Azure.SignalR.Common; - -namespace Microsoft.Azure.SignalR; - -#nullable enable - -internal partial class MicrosoftEntraAccessKey -{ - internal sealed class RestApiEndpoint - { - public string Audience { get; } - - public string Token { get; } - - public RestApiEndpoint(string endpoint, string token) - { - Audience = endpoint; - Token = token; - } - } - - internal sealed class RestClient - { - private readonly IHttpClientFactory _httpClientFactory; - - public RestClient(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - } - - public Task SendAsync( - RestApiEndpoint api, - HttpMethod httpMethod, - Func>? handleExpectedResponseAsync = null, - CancellationToken cancellationToken = default) - { - return SendAsyncCore(Constants.HttpClientNames.UserDefault, api, httpMethod, handleExpectedResponseAsync, cancellationToken); - } - - private async Task ThrowExceptionOnResponseFailureAsync(HttpResponseMessage response) - { - if (response.IsSuccessStatusCode) - { - return; - } - - var detail = await response.Content.ReadAsStringAsync(); - -#if NET5_0_OR_GREATER - var innerException = new HttpRequestException( - $"Response status code does not indicate success: {(int)response.StatusCode} ({response.ReasonPhrase})", null, response.StatusCode); -#else - var innerException = new HttpRequestException( - $"Response status code does not indicate success: {(int)response.StatusCode} ({response.ReasonPhrase})"); -#endif - throw response.StatusCode switch - { - HttpStatusCode.BadRequest => new AzureSignalRInvalidArgumentException(response.RequestMessage?.RequestUri?.ToString(), innerException, detail), - HttpStatusCode.Unauthorized => new AzureSignalRUnauthorizedException(response.RequestMessage?.RequestUri?.ToString(), innerException), - HttpStatusCode.NotFound => new AzureSignalRInaccessibleEndpointException(response.RequestMessage?.RequestUri?.ToString(), innerException), - _ => new AzureSignalRRuntimeException(response.RequestMessage?.RequestUri?.ToString(), innerException), - }; - } - - private async Task SendAsyncCore( - string httpClientName, - RestApiEndpoint api, - HttpMethod httpMethod, - Func>? handleExpectedResponseAsync = null, - CancellationToken cancellationToken = default) - { - using var httpClient = _httpClientFactory.CreateClient(httpClientName); - using var request = BuildRequest(api, httpMethod); - - try - { - using var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken); - if (handleExpectedResponseAsync == null) - { - await ThrowExceptionOnResponseFailureAsync(response); - } - else - { - if (!await handleExpectedResponseAsync(response)) - { - await ThrowExceptionOnResponseFailureAsync(response); - } - } - } - catch (HttpRequestException ex) - { - throw new AzureSignalRException($"An error happened when making request to {request.RequestUri}", ex); - } - } - - private HttpRequestMessage BuildRequest(RestApiEndpoint api, HttpMethod httpMethod) - { - return GenerateHttpRequest(api.Audience, httpMethod, api.Token); - } - - private HttpRequestMessage GenerateHttpRequest(string url, HttpMethod httpMethod, string tokenString) - { - var request = new HttpRequestMessage(httpMethod, new Uri(url)); - request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tokenString); - return request; - } - } -} \ No newline at end of file diff --git a/src/Microsoft.Azure.SignalR.Common/Auth/MicrosoftEntra/MicrosoftEntraAccessKey.cs b/src/Microsoft.Azure.SignalR.Common/Auth/MicrosoftEntra/MicrosoftEntraAccessKey.cs index 681636f54..f52f427bd 100644 --- a/src/Microsoft.Azure.SignalR.Common/Auth/MicrosoftEntra/MicrosoftEntraAccessKey.cs +++ b/src/Microsoft.Azure.SignalR.Common/Auth/MicrosoftEntra/MicrosoftEntraAccessKey.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; @@ -17,10 +18,14 @@ namespace Microsoft.Azure.SignalR; -internal partial class MicrosoftEntraAccessKey : AccessKey +#nullable enable + +internal class MicrosoftEntraAccessKey : AccessKey { internal static readonly TimeSpan GetAccessKeyTimeout = TimeSpan.FromSeconds(100); + internal Func> HttpResponseHandler; + private const int GetAccessKeyIntervalInMinute = 55; private const int GetAccessKeyMaxRetryTimes = 3; @@ -37,11 +42,11 @@ internal partial class MicrosoftEntraAccessKey : AccessKey private static readonly TimeSpan GetAccessKeyRetryInterval = TimeSpan.FromSeconds(3); - private readonly TaskCompletionSource _initializedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly TaskCompletionSource _initializedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - private volatile bool _isAuthorized = false; + private readonly IHttpClientFactory _httpClientFactory; - private Exception _lastException; + private volatile bool _isAuthorized = false; private DateTime _lastUpdatedTime = DateTime.MinValue; @@ -52,7 +57,7 @@ private set { if (value) { - _lastException = null; + LastException = null; } _lastUpdatedTime = DateTime.UtcNow; _isAuthorized = value; @@ -62,22 +67,30 @@ private set public TokenCredential TokenCredential { get; } + internal Exception? LastException { get; private set; } + internal string GetAccessKeyUrl { get; } internal bool HasExpired => DateTime.UtcNow - _lastUpdatedTime > TimeSpan.FromMinutes(GetAccessKeyIntervalInMinute * 2); - private Task InitializedTask => _initializedTcs.Task; + private Task InitializedTask => _initializedTcs.Task; - public MicrosoftEntraAccessKey(Uri endpoint, TokenCredential credential, Uri serverEndpoint = null) : base(endpoint) + public MicrosoftEntraAccessKey(Uri endpoint, + TokenCredential credential, + Uri? serverEndpoint = null, + IHttpClientFactory? httpClientFactory = null) : base(endpoint) { var authorizeUri = (serverEndpoint ?? endpoint).Append("/api/v1/auth/accessKey"); GetAccessKeyUrl = authorizeUri.AbsoluteUri; TokenCredential = credential; + HttpResponseHandler = HandleHttpResponseAsync; + + _httpClientFactory = httpClientFactory ?? HttpClientFactory.Instance; } public virtual async Task GetMicrosoftEntraTokenAsync(CancellationToken ctoken = default) { - Exception latest = null; + Exception? latest = null; for (var i = 0; i < GetMicrosoftEntraTokenMaxRetryTimes; i++) { try @@ -90,7 +103,7 @@ public virtual async Task GetMicrosoftEntraTokenAsync(CancellationToken latest = e; } } - throw latest; + throw latest ?? new InvalidOperationException(); } public override async Task GenerateAccessTokenAsync( @@ -107,7 +120,7 @@ public override async Task GenerateAccessTokenAsync( await task; return IsAuthorized ? await base.GenerateAccessTokenAsync(audience, claims, lifetime, algorithm, ctoken) - : throw new AzureSignalRAccessTokenNotAuthorizedException(TokenCredential.GetType().Name, _lastException); + : throw new AzureSignalRAccessKeyNotAvailableException(TokenCredential, LastException); } else { @@ -121,6 +134,22 @@ internal void UpdateAccessKey(string kid, string accessKey) IsAuthorized = true; } + internal async Task GetAccessKeyAsync(CancellationToken ctoken) + { + var accessToken = await GetMicrosoftEntraTokenAsync(ctoken); + + var request = new HttpRequestMessage(HttpMethod.Get, new Uri(GetAccessKeyUrl)); + request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + + var httpClient = _httpClientFactory.CreateClient(Constants.HttpClientNames.UserDefault); + + var response = await httpClient.SendAsync(request, ctoken); + + await HandleHttpResponseAsync(response); + + await ThrowExceptionOnResponseFailureAsync(response); + } + internal async Task UpdateAccessKeyAsync(CancellationToken ctoken = default) { var delta = DateTime.UtcNow - _lastUpdatedTime; @@ -139,18 +168,16 @@ internal async Task UpdateAccessKeyAsync(CancellationToken ctoken = default) var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(source.Token, ctoken); try { - var token = await GetMicrosoftEntraTokenAsync(linkedSource.Token); - await GetAccessKeyInternalAsync(token, linkedSource.Token); - return; + await GetAccessKeyAsync(linkedSource.Token); } catch (OperationCanceledException e) { - _lastException = e; + LastException = e; break; } catch (Exception e) { - _lastException = e; + LastException = e; try { await Task.Delay(GetAccessKeyRetryInterval, ctoken); @@ -165,32 +192,58 @@ internal async Task UpdateAccessKeyAsync(CancellationToken ctoken = default) IsAuthorized = false; } - private async Task GetAccessKeyInternalAsync(string accessToken, CancellationToken ctoken = default) + private static async Task ThrowExceptionOnResponseFailureAsync(HttpResponseMessage response) { - var api = new RestApiEndpoint(GetAccessKeyUrl, accessToken); - var client = new RestClient(HttpClientFactory.Instance); - await client.SendAsync( - api, - HttpMethod.Get, - handleExpectedResponseAsync: HandleHttpResponseAsync, - cancellationToken: ctoken); + if (response.IsSuccessStatusCode) + { + return; + } + + var detail = await response.Content.ReadAsStringAsync(); + +#if NET5_0_OR_GREATER + var innerException = new HttpRequestException( + $"Response status code does not indicate success: {(int)response.StatusCode} ({response.ReasonPhrase})", + null, + response.StatusCode); +#else + var innerException = new HttpRequestException( + $"Response status code does not indicate success: {(int)response.StatusCode} ({response.ReasonPhrase})"); +#endif + + var requestUri = response.RequestMessage?.RequestUri?.ToString(); + throw response.StatusCode switch + { + HttpStatusCode.BadRequest => new AzureSignalRInvalidArgumentException(requestUri, innerException, detail), + HttpStatusCode.Unauthorized => new AzureSignalRUnauthorizedException(requestUri, innerException), + HttpStatusCode.Forbidden => new AzureSignalRForbiddenException(requestUri, innerException), + HttpStatusCode.NotFound => new AzureSignalRInaccessibleEndpointException(requestUri, innerException), + _ => new AzureSignalRRuntimeException(requestUri, innerException), + }; } private async Task HandleHttpResponseAsync(HttpResponseMessage response) { - if (response.StatusCode != HttpStatusCode.OK) + string content; + if (response.StatusCode == HttpStatusCode.Forbidden) + { + content = await response.Content.ReadAsStringAsync(); + response.ReasonPhrase = content.Contains("nginx") ? Constants.IngressDenied : Constants.RuntimeDenied; + return false; + } + else if (response.StatusCode != HttpStatusCode.OK) { return false; } - var json = await response.Content.ReadAsStringAsync(); - var obj = JObject.Parse(json); + content = await response.Content.ReadAsStringAsync(); + var json = JObject.Parse(content); - if (!obj.TryGetValue("KeyId", out var keyId) || keyId.Type != JTokenType.String) + if (!json.TryGetValue("KeyId", out var keyId) || keyId.Type != JTokenType.String) { throw new AzureSignalRException("Missing required field."); } - if (!obj.TryGetValue("AccessKey", out var key) || key.Type != JTokenType.String) + if (!json.TryGetValue("AccessKey", out var key) || key.Type != JTokenType.String) { throw new AzureSignalRException("Missing required field."); } diff --git a/src/Microsoft.Azure.SignalR.Common/Constants.cs b/src/Microsoft.Azure.SignalR.Common/Constants.cs index 6e102e75d..fb5ebf18f 100644 --- a/src/Microsoft.Azure.SignalR.Common/Constants.cs +++ b/src/Microsoft.Azure.SignalR.Common/Constants.cs @@ -17,6 +17,10 @@ internal static class Constants public const string AsrsIsDiagnosticClient = "Asrs-Is-Diagnostic-Client"; + public const string IngressDenied = "Nginx denied the access, please check your Networking settings."; + + public const string RuntimeDenied = "Azure SignalR service denied the access, please check your Access control (IAM) settings."; + public static class Keys { public const string AzureSignalRSectionKey = "Azure:SignalR"; diff --git a/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRAccessKeyNotAvailableException.cs b/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRAccessKeyNotAvailableException.cs new file mode 100644 index 000000000..6c67fd5dd --- /dev/null +++ b/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRAccessKeyNotAvailableException.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using Azure.Core; + +namespace Microsoft.Azure.SignalR.Common; + +internal class AzureSignalRAccessKeyNotAvailableException : AzureSignalRException +{ + private const string Template = "The [{0}] is not available to generate access tokens for negotiation."; + + public AzureSignalRAccessKeyNotAvailableException(TokenCredential credential, Exception innerException) : + base(BuildExceptionMessage(credential), innerException) + { + } + + private static string BuildExceptionMessage(TokenCredential credential) + { + return string.Format(Template, credential.GetType().Name); + } +} diff --git a/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRAccessTokenNotAuthorizedException.cs b/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRAccessTokenNotAuthorizedException.cs index aeb32d4ad..16b726b28 100644 --- a/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRAccessTokenNotAuthorizedException.cs +++ b/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRAccessTokenNotAuthorizedException.cs @@ -8,6 +8,7 @@ namespace Microsoft.Azure.SignalR.Common /// /// The exception throws when AccessKey is not authorized. /// + [Obsolete] public class AzureSignalRAccessTokenNotAuthorizedException : AzureSignalRException { private const string Postfix = " appears to lack the permission to generate access tokens, see innerException for more details."; diff --git a/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRForbiddenException.cs b/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRForbiddenException.cs new file mode 100644 index 000000000..c9f0e31b2 --- /dev/null +++ b/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRForbiddenException.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; + +namespace Microsoft.Azure.SignalR.Common; + +[Serializable] +public class AzureSignalRForbiddenException : AzureSignalRException +{ + private const string ErrorMessage = "Azure SignalR service denied the access with status code 403."; + + public AzureSignalRForbiddenException(string requestUri, Exception innerException) : base( + string.IsNullOrEmpty(requestUri) ? ErrorMessage : $"{ErrorMessage} Request Uri: {requestUri}", + innerException) + { + } +} diff --git a/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRUnauthorizedException.cs b/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRUnauthorizedException.cs index b08ccc9fa..3e6de9e87 100644 --- a/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRUnauthorizedException.cs +++ b/src/Microsoft.Azure.SignalR.Common/Exceptions/AzureSignalRUnauthorizedException.cs @@ -4,26 +4,27 @@ using System; using System.Runtime.Serialization; -namespace Microsoft.Azure.SignalR.Common +namespace Microsoft.Azure.SignalR.Common; + +[Serializable] +public class AzureSignalRUnauthorizedException : AzureSignalRException { - [Serializable] - public class AzureSignalRUnauthorizedException : AzureSignalRException - { - private const string ErrorMessage = "Authorization failed. If you were using AccessKey, please check connection string and see if the AccessKey is correct. If you were using Azure Active Directory, please note that the role assignments will take up to 30 minutes to take effect if it was added recently."; + private const string ErrorMessage = "Azure SignalR service denied the access with status code 401."; - public AzureSignalRUnauthorizedException(string requestUri, Exception innerException) : base(string.IsNullOrEmpty(requestUri) ? ErrorMessage : $"{ErrorMessage} Request Uri: {requestUri}", innerException) - { - } + public AzureSignalRUnauthorizedException(string requestUri, Exception innerException) : base( + string.IsNullOrEmpty(requestUri) ? ErrorMessage : $"{ErrorMessage} Request Uri: {requestUri}", + innerException) + { + } - internal AzureSignalRUnauthorizedException(Exception innerException) : base(ErrorMessage, innerException) - { - } + internal AzureSignalRUnauthorizedException(Exception innerException) : base(ErrorMessage, innerException) + { + } #if NET8_0_OR_GREATER - [Obsolete] + [Obsolete] #endif - protected AzureSignalRUnauthorizedException(SerializationInfo info, StreamingContext context) : base(info, context) - { - } + protected AzureSignalRUnauthorizedException(SerializationInfo info, StreamingContext context) : base(info, context) + { } } \ No newline at end of file diff --git a/src/Microsoft.Azure.SignalR/Utilities/ServiceRouteHelper.cs b/src/Microsoft.Azure.SignalR/Utilities/ServiceRouteHelper.cs index 0c722e755..4bca25886 100644 --- a/src/Microsoft.Azure.SignalR/Utilities/ServiceRouteHelper.cs +++ b/src/Microsoft.Azure.SignalR/Utilities/ServiceRouteHelper.cs @@ -55,7 +55,7 @@ public static async Task RedirectToService(HttpContext context, IList)initializedTcsField.GetValue(key); - var lastExceptionFields = typeof(MicrosoftEntraAccessKey).GetField("_lastException", BindingFlags.NonPublic | BindingFlags.Instance); - await key.UpdateAccessKeyAsync().OrTimeout(TimeSpan.FromSeconds(30)); var actualLastUpdatedTime = Assert.IsType(lastUpdatedTimeField.GetValue(key)); @@ -89,14 +91,14 @@ public async Task TestUpdateAccessKeyAsyncShouldSkip(bool isAuthorized, int time { Assert.Equal(isAuthorized, Assert.IsType(isAuthorizedField.GetValue(key))); Assert.Equal(lastUpdatedTime, actualLastUpdatedTime); - Assert.Null(lastExceptionFields.GetValue(key)); + Assert.Null(key.LastException); Assert.False(initializedTcs.Task.IsCompleted); } else { Assert.False(Assert.IsType(isAuthorizedField.GetValue(key))); Assert.True(lastUpdatedTime < actualLastUpdatedTime); - Assert.NotNull(Assert.IsType(lastExceptionFields.GetValue(key))); + Assert.NotNull(Assert.IsType(key.LastException)); Assert.True(initializedTcs.Task.IsCompleted); } } @@ -118,7 +120,7 @@ public async Task TestInitializeFailed() await key.UpdateAccessKeyAsync(); - var exception = await Assert.ThrowsAsync( + var exception = await Assert.ThrowsAsync( async () => await key.GenerateAccessTokenAsync(audience, claims, lifetime, algorithm) ); Assert.IsType(exception.InnerException); @@ -141,16 +143,78 @@ public async Task TestUpdateAccessKeyAfterInitializeFailed() await key.UpdateAccessKeyAsync(); - var exception = await Assert.ThrowsAsync( + var exception = await Assert.ThrowsAsync( async () => await key.GenerateAccessTokenAsync(audience, claims, lifetime, algorithm) ); Assert.IsType(exception.InnerException); - var lastExceptionFields = typeof(MicrosoftEntraAccessKey).GetField("_lastException", BindingFlags.NonPublic | BindingFlags.Instance); - - Assert.NotNull(lastExceptionFields.GetValue(key)); + Assert.NotNull(key.LastException); var (kid, accessKey) = ("foo", DefaultSigningKey); key.UpdateAccessKey(kid, accessKey); - Assert.Null(lastExceptionFields.GetValue(key)); + Assert.Null(key.LastException); + } + + [Theory] + [InlineData(Constants.IngressDenied, "nginx")] + [InlineData(Constants.RuntimeDenied, "http")] + public async Task ThrowForbiddenExceptionTest(string expected, string responseContent) + { + var endpoint = new Uri("https://test-aad-signalr.service.signalr.net"); + + var message = new HttpResponseMessage(HttpStatusCode.Forbidden) + { + Content = new MockedHttpContent(responseContent) + }; + var key = new MicrosoftEntraAccessKey( + endpoint, + new TestTokenCredential(), + httpClientFactory: new MockedHttpClientFactory(message) + ); + + await key.UpdateAccessKeyAsync(); + + Assert.False(key.IsAuthorized); + var ex = Assert.IsType(key.LastException); + Assert.Contains(expected, ex.InnerException.Message); + } + + private sealed class MockedHttpClientFactory(HttpResponseMessage message) : IHttpClientFactory + { + public HttpClient CreateClient(string name) => new MockedHttpClient(message); + } + + private sealed class MockedHttpClient(HttpResponseMessage message) : HttpClient + { + public override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + return Task.FromResult(message); + } + } + + private sealed class MockedHttpContent(string content) : HttpContent + { + protected override Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + return stream.WriteAsync(Encoding.UTF8.GetBytes(content)).AsTask(); + } + + protected override bool TryComputeLength(out long length) + { + length = content.Length; + return true; + } + } + + private sealed class TestTokenCredential : TokenCredential + { + public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return new AccessToken("foo", DateTimeOffset.UtcNow.Add(TimeSpan.FromHours(1))); + } + + public override ValueTask GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) + { + return ValueTask.FromResult(GetToken(requestContext, cancellationToken)); + } } } diff --git a/test/Microsoft.Azure.SignalR.Common.Tests/Auth/MicrosoftEntraApplicationTests.cs b/test/Microsoft.Azure.SignalR.Common.Tests/Auth/MicrosoftEntraApplicationTests.cs index f6f1f6881..ac16832d9 100644 --- a/test/Microsoft.Azure.SignalR.Common.Tests/Auth/MicrosoftEntraApplicationTests.cs +++ b/test/Microsoft.Azure.SignalR.Common.Tests/Auth/MicrosoftEntraApplicationTests.cs @@ -17,10 +17,12 @@ public class MicrosoftEntraApplicationTests private const string IssuerEndpoint = "https://sts.windows.net/"; private const string TestClientId = ""; + private const string TestClientSecret = ""; + private const string TestTenantId = ""; - private static readonly string[] DefaultScopes = new string[] { "https://signalr.azure.com/.default" }; + private static readonly string[] DefaultScopes = ["https://signalr.azure.com/.default"]; [Fact(Skip = "Provide valid Microsoft Entra application options")] public async Task TestAcquireAccessToken() @@ -44,20 +46,11 @@ public async Task TestGetMicrosoftEntraTokenAndAuthenticate() var p = new TokenValidationParameters() { - ValidateLifetime = true, + IssuerSigningKeys = keys, + IssuerValidator = (string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) => issuer.StartsWith(IssuerEndpoint) ? IssuerEndpoint : throw new SecurityTokenInvalidIssuerException(), ValidateAudience = false, - - IssuerValidator = (string issuer, SecurityToken securityToken, TokenValidationParameters validationParameters) => - { - if (issuer.StartsWith(IssuerEndpoint)) - { - return IssuerEndpoint; - } - throw new SecurityTokenInvalidIssuerException(); - }, - ValidateIssuerSigningKey = true, - IssuerSigningKeys = keys, + ValidateLifetime = true, }; var handler = new JwtSecurityTokenHandler(); diff --git a/test/Microsoft.Azure.SignalR.Common.Tests/AzureSignalRExceptionTests.cs b/test/Microsoft.Azure.SignalR.Common.Tests/AzureSignalRExceptionTests.cs deleted file mode 100644 index 19a422797..000000000 --- a/test/Microsoft.Azure.SignalR.Common.Tests/AzureSignalRExceptionTests.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. - -using System; -using Azure.Identity; -using Xunit; - -namespace Microsoft.Azure.SignalR.Common.Tests; - -public class AzureSignalRExceptionTests -{ - [Fact] - public void CredentialNotAuthorizedTest() - { - var credential = new DefaultAzureCredential(); - var inner = new Exception(); - var exception = new AzureSignalRAccessTokenNotAuthorizedException(credential.GetType().Name, inner); - Assert.StartsWith(nameof(DefaultAzureCredential), exception.Message); - } -}