Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ public WebPubSubServiceClient(System.Uri endpoint, string hub, Azure.AzureKeyCre
public virtual System.Threading.Tasks.Task<Azure.Response> AddUserToGroupAsync(string group, string userId, Azure.RequestOptions options = null) { throw null; }
public virtual Azure.Response<bool> CheckPermission(Azure.Messaging.WebPubSub.WebPubSubPermission permission, string connectionId, string targetName = null, Azure.RequestOptions options = null) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<bool>> 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<Azure.Response> 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<Azure.Response> CloseConnectionAsync(string connectionId, string reason = null, Azure.RequestOptions options = null) { throw null; }
public virtual Azure.Response<bool> ConnectionExists(string connectionId, Azure.RequestOptions options = null) { throw null; }
public virtual System.Threading.Tasks.Task<Azure.Response<bool>> 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; }
Expand Down Expand Up @@ -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,
Expand Down
29 changes: 29 additions & 0 deletions sdk/webpubsub/Azure.Messaging.WebPubSub/src/ApimPolicy.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// API Management Policy
/// </summary>
internal partial class ApimPolicy : HttpPipelineSynchronousPolicy
{
private Uri _apimEndpoint;

public ApimPolicy(Uri apimEndpoint) => _apimEndpoint = apimEndpoint;

/// <inheritdoc/>
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);
}
}
}

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ internal partial class WebPubSubAuthenticationPolicy : HttpPipelineSynchronousPo
/// <inheritdoc/>
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);

Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// <auto-generated/>

#nullable disable

using System;
using Azure.Core;

namespace Azure.Messaging.WebPubSub
{
/// <summary> Client options for WebPubSubServiceClient. </summary>
public partial class WebPubSubServiceClientOptions : ClientOptions
{
/// <summary>
/// Reverse proxy.
/// </summary>
public Uri ReverseProxyEndpoint { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@
"webpubsub"
],
"summary": "Close the client connection.",
"operationId": "WebPubSubService_CloseClientConnection",
"operationId": "WebPubSubService_CloseConnection",
"parameters": [
{
"in": "path",
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}