diff --git a/sdk/webpubsub/Azure.Messaging.WebPubSub/api/Azure.Messaging.WebPubSub.netstandard2.0.cs b/sdk/webpubsub/Azure.Messaging.WebPubSub/api/Azure.Messaging.WebPubSub.netstandard2.0.cs index a87c8c5c96c0..f70a0846a9a6 100644 --- a/sdk/webpubsub/Azure.Messaging.WebPubSub/api/Azure.Messaging.WebPubSub.netstandard2.0.cs +++ b/sdk/webpubsub/Azure.Messaging.WebPubSub/api/Azure.Messaging.WebPubSub.netstandard2.0.cs @@ -21,8 +21,8 @@ public WebPubSubServiceClient(System.Uri endpoint, string hub, Azure.AzureKeyCre public virtual System.Threading.Tasks.Task AddUserToGroupAsync(string group, string userId, Azure.RequestOptions options = null) { throw null; } public virtual Azure.Response CheckPermission(Azure.Messaging.WebPubSub.WebPubSubPermission permission, string connectionId, string targetName = null, Azure.RequestOptions options = null) { throw null; } public virtual System.Threading.Tasks.Task> CheckPermissionAsync(Azure.Messaging.WebPubSub.WebPubSubPermission permission, string connectionId, string targetName = null, Azure.RequestOptions options = null) { throw null; } - public virtual Azure.Response CloseClientConnection(string connectionId, string reason = null, Azure.RequestOptions options = null) { throw null; } - public virtual System.Threading.Tasks.Task CloseClientConnectionAsync(string connectionId, string reason = null, Azure.RequestOptions options = null) { throw null; } + public virtual Azure.Response CloseConnection(string connectionId, string reason = null, Azure.RequestOptions options = null) { throw null; } + public virtual System.Threading.Tasks.Task CloseConnectionAsync(string connectionId, string reason = null, Azure.RequestOptions options = null) { throw null; } public virtual Azure.Response ConnectionExists(string connectionId, Azure.RequestOptions options = null) { throw null; } public virtual System.Threading.Tasks.Task> ConnectionExistsAsync(string connectionId, Azure.RequestOptions options = null) { throw null; } public virtual System.Uri GenerateClientAccessUri(System.DateTimeOffset expiresAt, string userId = null, params string[] roles) { throw null; } @@ -61,6 +61,7 @@ public WebPubSubServiceClient(System.Uri endpoint, string hub, Azure.AzureKeyCre public partial class WebPubSubServiceClientOptions : Azure.Core.ClientOptions { public WebPubSubServiceClientOptions(Azure.Messaging.WebPubSub.WebPubSubServiceClientOptions.ServiceVersion version = Azure.Messaging.WebPubSub.WebPubSubServiceClientOptions.ServiceVersion.V2021_05_01_preview) { } + public System.Uri ReverseProxyEndpoint { get { throw null; } set { } } public enum ServiceVersion { V2021_05_01_preview = 1, diff --git a/sdk/webpubsub/Azure.Messaging.WebPubSub/src/ApimPolicy.cs b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/ApimPolicy.cs new file mode 100644 index 000000000000..41c00baa980a --- /dev/null +++ b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/ApimPolicy.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using Azure.Core; +using Azure.Core.Pipeline; + +namespace Azure.Messaging.WebPubSub +{ + /// + /// API Management Policy + /// + internal partial class ApimPolicy : HttpPipelineSynchronousPolicy + { + private Uri _apimEndpoint; + + public ApimPolicy(Uri apimEndpoint) => _apimEndpoint = apimEndpoint; + + /// + public override void OnSendingRequest(HttpMessage message) + { + var originalUri = message.Request.Uri.ToUri(); + var path = originalUri.PathAndQuery; + message.Request.Uri.Reset(_apimEndpoint); + message.Request.Uri.AppendPath(path, escape: false); + WebPubSubAuthenticationPolicy.SetAudience(message, originalUri); + } + } +} diff --git a/sdk/webpubsub/Azure.Messaging.WebPubSub/src/Generated/WebPubSubServiceClient.cs b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/Generated/WebPubSubServiceClient.cs index 113f2a2f94bf..839bb09c927e 100644 --- a/sdk/webpubsub/Azure.Messaging.WebPubSub/src/Generated/WebPubSubServiceClient.cs +++ b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/Generated/WebPubSubServiceClient.cs @@ -251,16 +251,16 @@ private HttpMessage CreateConnectionExistsImplRequest(string connectionId, Reque /// The reason closing the client connection. /// The request options. #pragma warning disable AZC0002 - public virtual async Task CloseClientConnectionAsync(string connectionId, string reason = null, RequestOptions options = null) + public virtual async Task CloseConnectionAsync(string connectionId, string reason = null, RequestOptions options = null) #pragma warning restore AZC0002 { options ??= new RequestOptions(); - HttpMessage message = CreateCloseClientConnectionRequest(connectionId, reason, options); + HttpMessage message = CreateCloseConnectionRequest(connectionId, reason, options); if (options.PerCallPolicy != null) { message.SetProperty("RequestOptionsPerCallPolicyCallback", options.PerCallPolicy); } - using var scope = _clientDiagnostics.CreateScope("WebPubSubServiceClient.CloseClientConnection"); + using var scope = _clientDiagnostics.CreateScope("WebPubSubServiceClient.CloseConnection"); scope.Start(); try { @@ -292,16 +292,16 @@ public virtual async Task CloseClientConnectionAsync(string connection /// The reason closing the client connection. /// The request options. #pragma warning disable AZC0002 - public virtual Response CloseClientConnection(string connectionId, string reason = null, RequestOptions options = null) + public virtual Response CloseConnection(string connectionId, string reason = null, RequestOptions options = null) #pragma warning restore AZC0002 { options ??= new RequestOptions(); - HttpMessage message = CreateCloseClientConnectionRequest(connectionId, reason, options); + HttpMessage message = CreateCloseConnectionRequest(connectionId, reason, options); if (options.PerCallPolicy != null) { message.SetProperty("RequestOptionsPerCallPolicyCallback", options.PerCallPolicy); } - using var scope = _clientDiagnostics.CreateScope("WebPubSubServiceClient.CloseClientConnection"); + using var scope = _clientDiagnostics.CreateScope("WebPubSubServiceClient.CloseConnection"); scope.Start(); try { @@ -328,11 +328,11 @@ public virtual Response CloseClientConnection(string connectionId, string reason } } - /// Create Request for and operations. + /// Create Request for and operations. /// Target connection Id. /// The reason closing the client connection. /// The request options. - private HttpMessage CreateCloseClientConnectionRequest(string connectionId, string reason = null, RequestOptions options = null) + private HttpMessage CreateCloseConnectionRequest(string connectionId, string reason = null, RequestOptions options = null) { var message = Pipeline.CreateMessage(); var request = message.Request; diff --git a/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubAuthenticationPolicy.cs b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubAuthenticationPolicy.cs index 56e147a2ecc1..0bfa62050cbf 100644 --- a/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubAuthenticationPolicy.cs +++ b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubAuthenticationPolicy.cs @@ -25,7 +25,11 @@ internal partial class WebPubSubAuthenticationPolicy : HttpPipelineSynchronousPo /// public override void OnSendingRequest(HttpMessage message) { - string audience = message.Request.Uri.ToUri().AbsoluteUri; + string audience; + if (!TryGetAudience(message, out audience)) { + audience = message.Request.Uri.ToUri().AbsoluteUri; + } + var now = DateTimeOffset.UtcNow; var expiresAt = now + TimeSpan.FromMinutes(5); @@ -55,6 +59,25 @@ public override void OnSendingRequest(HttpMessage message) message.Request.Headers.SetValue(HttpHeader.Names.Authorization, headerValue); } + // this is to support API Management Server + private const string AUDIENCE_SETTING = nameof(WebPubSubAuthenticationPolicy) + ".Audience"; + public static void SetAudience(HttpMessage message, Uri audience) + { + message.SetProperty(AUDIENCE_SETTING, audience.AbsoluteUri); + } + + private static bool TryGetAudience(HttpMessage message, out string audience) + { + if (message.TryGetProperty(AUDIENCE_SETTING, out var jwtAudience) && + jwtAudience is string uri) + { + audience = uri; + return true; + } + audience = default; + return false; + } + private sealed class KeyBytesCache { public KeyBytesCache(string key) diff --git a/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubServiceClientOptions_extensions.cs b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubServiceClientOptions_extensions.cs new file mode 100644 index 000000000000..b2d5099d848c --- /dev/null +++ b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubServiceClientOptions_extensions.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +// + +#nullable disable + +using System; +using Azure.Core; + +namespace Azure.Messaging.WebPubSub +{ + /// Client options for WebPubSubServiceClient. + public partial class WebPubSubServiceClientOptions : ClientOptions + { + /// + /// Reverse proxy. + /// + public Uri ReverseProxyEndpoint { get; set; } + } +} diff --git a/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubServiceClient_extensions.cs b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubServiceClient_extensions.cs index 9c2dae708dda..be9b5a1273df 100644 --- a/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubServiceClient_extensions.cs +++ b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/WebPubSubServiceClient_extensions.cs @@ -57,9 +57,19 @@ public WebPubSubServiceClient(Uri endpoint, string hub, AzureKeyCredential crede _clientDiagnostics = new ClientDiagnostics(options); apiVersion = options.Version; + HttpPipelinePolicy[] perCallPolicies; + if (options.ReverseProxyEndpoint != null) + { + perCallPolicies = new HttpPipelinePolicy[] { new ApimPolicy(options.ReverseProxyEndpoint), new LowLevelCallbackPolicy() }; + } + else + { + perCallPolicies = new HttpPipelinePolicy[] { new LowLevelCallbackPolicy() }; + } + Pipeline = HttpPipelineBuilder.Build( options, - perCallPolicies: new HttpPipelinePolicy[] { new LowLevelCallbackPolicy() }, + perCallPolicies: perCallPolicies, perRetryPolicies: new HttpPipelinePolicy[] { new WebPubSubAuthenticationPolicy(credential) }, new ResponseClassifier() ); diff --git a/sdk/webpubsub/Azure.Messaging.WebPubSub/src/swagger/WebPubSub.json b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/swagger/WebPubSub.json index eabfa10aa84d..28eb7c82a61f 100644 --- a/sdk/webpubsub/Azure.Messaging.WebPubSub/src/swagger/WebPubSub.json +++ b/sdk/webpubsub/Azure.Messaging.WebPubSub/src/swagger/WebPubSub.json @@ -145,7 +145,7 @@ "webpubsub" ], "summary": "Close the client connection.", - "operationId": "WebPubSubService_CloseClientConnection", + "operationId": "WebPubSubService_CloseConnection", "parameters": [ { "in": "path", diff --git a/sdk/webpubsub/Azure.Messaging.WebPubSub/tests/WebPubSubPolicyTests.cs b/sdk/webpubsub/Azure.Messaging.WebPubSub/tests/WebPubSubPolicyTests.cs new file mode 100644 index 000000000000..bcd4777ea918 --- /dev/null +++ b/sdk/webpubsub/Azure.Messaging.WebPubSub/tests/WebPubSubPolicyTests.cs @@ -0,0 +1,39 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Threading.Tasks; +using Azure.Core; +using Azure.Core.Pipeline; +using Azure.Core.TestFramework; +using NUnit.Framework; + +namespace Azure.Messaging.WebPubSub.Tests +{ + [TestFixture] + public class WebPubSubPolicyTests + { + [Test] + public void ReverseProxyEndpointRedirection() + { + var mockResponse = new MockResponse(202); + var transport = new MockTransport(mockResponse); + + var wpsEndpoint = "https://wps.contoso.com/"; + var apimEndpoint = "https://apim.contoso.com/"; + var credentail = new AzureKeyCredential("abcdabcdabcdabcdabcdabcdabcdabcd"); + + var options = new WebPubSubServiceClientOptions(); + options.Transport = transport; + options.ReverseProxyEndpoint = new Uri(apimEndpoint); + + var client = new WebPubSubServiceClient(new Uri(wpsEndpoint), "test_hub", credentail, options); + + var response = client.SendToAll("Hello World!"); + Assert.AreEqual(202, response.Status); + + var requestUri = transport.SingleRequest.Uri.ToUri(); + Assert.AreEqual(new Uri(apimEndpoint).Host, requestUri.Host); + } + } +}