9

I have a BizTalk WCF-Custom receive location to which I have added a custom behavior:

public class SasTokenProviderEndpointBehavior : BehaviorExtensionElement, IEndpointBehavior
    {
        public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
        {
                var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(sharedAccessSecretName, sharedAccessKey);
                bindingParameters.Add(new TransportClientEndpointBehavior { TokenProvider = tokenProvider });         
        }
    }
}

parameter setup code omitted for brevity

This is adapted from a sample found at https://code.msdn.microsoft.com/How-to-integrate-BizTalk-07fada58#content - this author is widely respected in the BizTalk community and code of this kind has been in use for some years. All I am doing is adapting the method he uses, that is proven to work, to substitute a different TokenProvider.

I can see through debugging that this code runs and the TransportClientEndpointBehavior with correct parameters is added to the channel. However when the BizTalk receive location polls Service Bus, I see the following in the event log:

The adapter "WCF-Custom" raised an error message. Details "System.UnauthorizedAccessException: 40102: Missing authorization token, Resource:sb://[namespace].servicebus.windows.net/[queue]. TrackingId:452c2534-d3e6-400f-874f-09be324e9e11_G27, SystemTracker:[namespace].servicebus.windows.net:[queue], Timestamp:12/1/2016 11:38:56 AM ---> System.ServiceModel.FaultException: 40102: Missing authorization token, Resource:sb://[namespace].servicebus.windows.net/[queue]. TrackingId:452c2534-d3e6-400f-874f-09be324e9e11_G27, SystemTracker:[namespace].servicebus.windows.net:[queue], Timestamp:12/1/2016 11:38:56 AM

I cannot see any reason that the Azure Service Bus endpoint would return this error message except that because the token provider is not being used. Why would the channel ignore the TokenProvider and what do I have to do to pass the token correctly?

edit:

I have inspected the raw WCF message traffic for the port in question as well as one using the SB-Messaging adapter, which works as expected. The difference is that the SB-Messaging adapter's messages contain a SOAP header like:

<Authorization xmlns="http://schemas.microsoft.com/servicebus/2010/08/protocol/">SharedAccessSignature sr=[really long encoded string]</Authorization> and my custom binding port's messages do not. So it is true that the problem is a missing Authorization SOAP header; but the question persists - why isn't the channel adding this header?

edit #2:

I have decompiled Microsoft.ServiceBus.dll and I believe I've found the class that actually creates the WCF messsage, Microsoft.ServiceBus.Messaging.Sbmp.SbmpMessageCreator. It has this method:

private Message CreateWcfMessageInternal(string action, object body, bool includeToken, string parentLinkId, RetryPolicy policy, TrackingContext trackingContext, RequestInfo requestInfo)
    {
      Message message = Message.CreateMessage(this.messageVersion, action, body);
      MessageHeaders headers = message.Headers;
      headers.To = this.logicalAddress;
      string sufficientClaims = this.GetSufficientClaims();
      if (this.linkInfo != null)
      {
        if (!string.IsNullOrEmpty(this.linkInfo.TransferDestinationEntityAddress))
        {
          SecurityToken authorizationToken = this.GetAuthorizationToken(this.linkInfo.TransferDestinationEntityAddress, sufficientClaims);
          if (authorizationToken != null)
          {
            SimpleWebSecurityToken webSecurityToken = (SimpleWebSecurityToken) authorizationToken;
            if (webSecurityToken != null)
              this.linkInfo.TransferDestinationAuthorizationToken = webSecurityToken.Token;
          }
        }
        this.linkInfo.AddTo(headers);
      }
      if (includeToken)
      {
        ServiceBusAuthorizationHeader authorizationHeader = this.GetAuthorizationHeader(sufficientClaims);
        if (authorizationHeader != null)
          headers.Add((MessageHeader) authorizationHeader);
      }
      if (this.messagingFactory.FaultInjectionInfo != null)
        this.messagingFactory.FaultInjectionInfo.AddToHeader(message);
      if (!string.IsNullOrWhiteSpace(parentLinkId))
        message.Properties["ParentLinkId"] = (object) parentLinkId;
      if (trackingContext != null)
        TrackingIdHeader.TryAddOrUpdate(headers, trackingContext.TrackingId);
      MessageExtensionMethods.AddHeaderIfNotNull<RequestInfo>(message, "RequestInfo", "http://schemas.microsoft.com/netservices/2011/06/servicebus", requestInfo);
      return message;
    }

So thinking about it logically, there are two reasons the Authorization header would be missing:

  • includeToken is false (Why would this be so?)
  • GetAuthorizationHeader() returns null (Why?)

edit #3:

I have compiled and run the example code and this works. The only significant difference between my code and his is that mine includes a line which calls out to Azure Key Vault:

var kv = new KeyVaultClient(this.GetAccessToken);
var key = kv.GetSecretAsync(this.KeyVaultUri.AbsoluteUri, this.SharedAccessSecretName).Result;
var sharedAccessKey = key.Value;
var tokenProvider = TokenProvider.CreateSharedAccessSignatureTokenProvider(
            this.SharedAccessSecretName, 
            sharedAccessKey);
bindingParameters.Add(new TransportClientEndpointBehavior { TokenProvider = tokenProvider });

This is an asynchronous method that returns a Task. Can it be that blocking on the result of this Task somehow doesn't do what would be expected in certain situations, and this is messing up the configuration of the WCF channel somehow? As I said, I am certain this code runs and assigns the TokenProvider. I am now merely not certain when it runs.

Julian Declercq
  • 1,536
  • 3
  • 17
  • 32
Tom W
  • 5,108
  • 4
  • 30
  • 52
  • Have you tried debugging by attaching Visual Studio to the Host Instance that the port is running under? – Dijkgraaf Dec 02 '16 at 21:51
  • @Dijkgraaf yes, that's how I can prove to myself that the TokenProvider is assigned where it's supposed to be. The only other idea that has occurred to me is that it may be that in normal Service Bus scenarios there is a special Channel Factory that does 'something' to make this work, whereas presumably BizTalk would have either a bog standard ChannelFactory or a BizTalk-specific one, neither of which would know anything about that special logic. I'm not in a position to investigate now but I will follow up this possibility at some point. – Tom W Dec 03 '16 at 12:28
  • I am working my way through an example that ought to demonstrate the previous assertion - I am becoming more confident that this is the case. [this example](https://code.msdn.microsoft.com/Brokered-Messaging-WCF-0a526451) suggests that in order for Service Bus scenarios to work in WCF one must call CustomBinding.BuildChannelFactory; this delegates down to the transport binding element to actually create the channel. Calling this in a unit test returns a ServiceBusChannelFactory - the WCF adapter does **not** do this. I believe this is why Paolo's `SessionChannelBindingElement` is important. – Tom W Dec 05 '16 at 14:01

1 Answers1

2

D'OH!

I had neglected to realise that the very old version of Microsoft.ServiceBus.dll we still have in the solution for interop with the (equally old) on premises version of Service Bus (Service Bus for Windows Server) was the one referenced by my project. For whatever reason this version just doesn't do what it's supposed to, and doesn't give any indication that it's bypassing the intended behaviour. Updating to have the current NuGet package for Service Bus fixes the problem.

Tom W
  • 5,108
  • 4
  • 30
  • 52
  • I up-voted for your courageousness and willingness to come back and answer. This closes the loop on what could have been lingering questions in the rest of our minds. Thank you. – T-Heron Dec 11 '16 at 21:47