Bug 562 - WCF: BasicHttpSecurityMode.TransportWithMessageCredential throws NotImplementedException
Summary: WCF: BasicHttpSecurityMode.TransportWithMessageCredential throws NotImplement...
Status: NEW
Alias: None
Product: Class Libraries
Classification: Mono
Component: WCF assemblies (show other bugs)
Version: unspecified
Hardware: Macintosh Mac OS
: --- normal
Target Milestone: Untriaged
Assignee: Bugzilla
URL:
Depends on:
Blocks:
 
Reported: 2011-08-31 07:18 UTC by René Ruppert
Modified: 2017-08-31 15:03 UTC (History)
6 users (show)

Tags:
Is this bug a regression?: ---
Last known good build:


Attachments
MonoDevelop 2.6 Project using WCF (16.98 KB, application/x-zip-compressed)
2011-08-31 07:18 UTC, René Ruppert
Details


Notice (2018-05-24): bugzilla.xamarin.com is now in read-only mode.

Please join us on Visual Studio Developer Community and in the Xamarin and Mono organizations on GitHub to continue tracking issues. Bugzilla will remain available for reference in read-only mode. We will continue to work on open Bugzilla bugs, copy them to the new locations as needed for follow-up, and add the new items under Related Links.

Our sincere thanks to everyone who has contributed on this bug tracker over the years. Thanks also for your understanding as we make these adjustments and improvements for the future.


Please create a new report for Bug 562 on GitHub or Developer Community if you have new information to add and do not yet see a matching new report.

If the latest results still closely match this report, you can use the original description:

  • Export the original title and description: GitHub Markdown or Developer Community HTML
  • Copy the title and description into the new report. Adjust them to be up-to-date if needed.
  • Add your new information.

In special cases on GitHub you might also want the comments: GitHub Markdown with public comments

Related Links:
Status:
NEW

Description René Ruppert 2011-08-31 07:18:06 UTC
Created attachment 220 [details]
MonoDevelop 2.6 Project using WCF

I'm trying to access a service using WCF.
Attached find a Monodevelop 2.6 test project that demonstrates the issue.

I can use BasicHttpSecurityMode.None or BasicHttpSecurityMode.Transport, but BasicHttpSecurityMode.TransportWithMessageCredential will fail with a NotImplementedException.

How I'm I supposed to have secure communication if this is not supported?

Tested with Mono 2.10.5

Ultimately, I need to use this in Monotouch but I thought I'd submit the bug here. But maybe the MT team can give feedback too?

Thanks!
Comment 1 Sebastien Pouliot 2011-08-31 11:30:14 UTC
Partial answer follow...

> How I'm I supposed to have secure communication if this is not supported?

BasicHttpSecurityMode.Transport will provide secure communication using HTTPS.

BasicHttpSecurityMode.TransportWithMessageCredential use the same HTTPS transport but also provide client authentication using the SOAP message.

http://msdn.microsoft.com/en-us/library/system.servicemodel.securitymode.aspx


> But maybe the MT team can give feedback too?

Sure :-) MonoTouch 4.x had issues with BasicHttpSecurityMode.Transport but this is now fixed (see bug #380) and will be part of the next release(s). 

Otherwise MonoTouch is based on the 2.1 (Silverlight) profile, not the full WCF (so not all features are available, even if more features exists in Mono).
Comment 2 René Ruppert 2011-08-31 11:46:51 UTC
This means if I'm required to have TransportWithMessageCredential, there's no chance that it will be in Mono/Monotouch in near future?
Comment 3 Zoltan Varga 2011-12-08 08:12:09 UTC
-> wcf.
Comment 4 ToddG 2016-12-27 02:48:10 UTC
There still doesn't seem to be a good/convenient/simple solution to this problem.

I just went through all the proposed work-arounds detailed in (https://bugzilla.xamarin.com/show_bug.cgi?id=8020#c5) but I wasn't able to get any of them to work on Android.

  *  client.ClientCredentials.UserName (FAIL)
  *  TransportWithMessageCredential (FAIL this bug)
  *  IClientMessageInspector and IEndpointBehavior (FAIL ApplyClientBehavior never invoked )

https://forums.xamarin.com/discussion/6493/wcf-basichttpbinding-with-transportwithmessagecredential-username-failure-ios-and-android



Update requested ....
Comment 5 christophe 2017-05-09 18:13:07 UTC
Hi,

How can we rise importance of this issue?
I am waiting for a fix since a year now. This is a shame.

Please advise
Comment 6 Dean Parker 2017-08-31 14:56:14 UTC
I'm amazed this isn't  resolved. WCF support in Xamarin without authentication is not good.
Comment 7 christophe 2017-08-31 15:03:42 UTC
Hi,

I implemented it myself.

I would like to create a code project for it, but don't have time...

Look at this code:


using System;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;

namespace MobileCommon
{
    /// <summary>
    /// Class used to define only once the user password (independently from the generic types).
    /// </summary>
    public class BaseSafeWcfProxy
    {
        /// <summary>
        /// Holds user credentials to transfer to WCF server.
        /// This is to be set before calling Invoke or InvokeAsync.
        /// See: http://stackoverflow.com/questions/32677715/xamarin-wcf-iclientmessageinspector-custom-headers
        /// </summary>
        public static WcfSecurity Security
        { get; set; }

    }

    /// <summary>
    /// http://webandlife.blogspot.com/2012/08/proper-disposal-of-wcf-channels-against.html
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <remarks>Better to put this into another component</remarks>
    public class MobileSafeWcfProxy<T, TInterface> : BaseSafeWcfProxy where TInterface : class where T : System.ServiceModel.ClientBase<TInterface>, ICommunicationObject
    {
        protected Func<T> createClient = null;

        protected MobileSafeWcfProxy(Func<T> createClient)
        {
            this.createClient = createClient;
        }

        public virtual ProxyWrapper<T> Instance
        {
            get;
            private set;
        }

        /// <summary>
        /// This inner class is an IDisposable. When it gets disposed, it closes the inner WCG proxy object via Close and Abort depending on its current state.
        /// </summary>
        /// <typeparam name="T2"></typeparam>
        public class ProxyWrapper<T2> : IDisposable where T2 : System.ServiceModel.ClientBase<TInterface>, ICommunicationObject
        {
            public ProxyWrapper(T2 instance)
            {
                Proxy = instance;
            }

            public virtual T2 Proxy
            {
                get;
                private set;
            }

            bool disposed;
            public void Dispose()
            {
                Dispose(true);
                GC.SuppressFinalize(this);
            }

            private void Dispose(bool disposing)
            {
                if (!this.disposed)
                {
                    if (disposing)
                    {
                        Close();
                    }
                    this.disposed = true;
                }
            }
            public void Close()
            {
                bool success = false;
                if (Proxy == null || Proxy.State == CommunicationState.Closed)
                {
                    return;
                }
                try
                {
                    if (Proxy.State != CommunicationState.Faulted)
                    {
                        Proxy.Close();
                        success = true;
                    }
                }
                catch (Exception ex)
                {
                    App.MobileLogger.Error("MobileSafeWcfProxy", "Close", "error: " + ex.ToString());
                }
                finally
                {
                    if (!success)
                    {
                        try
                        {
                            Proxy.Abort();
                        }
                        catch (Exception)
                        {
                        }
                    }
                }
            }


            public object Invoke(Func<T2, object> p)
            {
                Exception realException = null;
                try
                {
#if PERFORMANCE
                    if (LogHelper.Get().IsDebug)
                    {
                        LogHelper.Get().Debug("---> PERF: before sync call");
                    }
#endif
                    object result = null;
                    using (new OperationContextScope(Proxy.InnerChannel))
                    {
                        // Add a SOAP Header to an outgoing request
                        // For transport with message credentials security:
                        // See: https://blogs.msdn.microsoft.com/wsdevsol/2014/02/07/adding-custom-messageheader-and-http-header-to-a-wcf-method-call-in-a-windows-store-app-or-windows-phone-app/
                        if (Security != null)
                        {

                            MessageHeader aMessageHeader = MessageHeader.CreateHeader("Security",
                                "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", Security);

                            OperationContext.Current.OutgoingMessageHeaders.Add(aMessageHeader);

                        }
                        result = p(Proxy);
                    }
#if PERFORMANCE
                    if (LogHelper.Get().IsDebug)
                    {
                        LogHelper.Get().Debug("---> PERF: after sync call");
                    }
#endif
                    return result;
                }
                catch (Exception ex)
                {
                    // Store exception to send it again later:
                    realException = ex;
                }
                finally
                {
                    Close();
                }

                // Delay exception throwing so that if an exception occurs in the Close method, then it does NOT
                // break the original exception throw:
                if (realException != null)
                {
                    throw new Exception("WCF Invoke error", realException);
                }
                return null;
            }

            private Action<object, object> onCompletedAction = null;

            public void InvokeAsync(Action<T2> call, string eventName, Action<object, object> onCompleted)
            {
                try
                {
                    if (call == null)
                    {
                        return;
                    }
                    onCompletedAction = onCompleted;

                    if (eventName != null)
                    {
                        // Idea here, is to register the OnCallCompleted method as generic XXXCompleted event handler.
                        // Then, this method takes care of delegating the completed call to the right handler.

                        // Retrieve the generic event handler:
                        MethodInfo method = this.GetType().GetTypeInfo().GetDeclaredMethod("OnCallCompleted");

                        // Register to completed event:
                        EventInfo eventInfo = Proxy.GetType().GetTypeInfo().GetDeclaredEvent(eventName);
                        if (eventInfo == null)
                        {
                            App.MobileLogger.Error("MobileSafeWcfProxy", "SafeWcfProxy.InvokeAsync", "error: invalid event name '" + eventName + "'. Cannot register to WCF method callback!");
                        }
                        else
                        {
                            Type type = eventInfo.EventHandlerType;
                            Delegate delegateInstance = method.CreateDelegate(type, this);

                            MethodInfo addHandler = eventInfo.AddMethod;
                            Object[] addHandlerArgs = { delegateInstance };
                            addHandler.Invoke(Proxy, addHandlerArgs);
                        }
                    }
#if PERFORMANCE
                    if (LogHelper.Get().IsDebug)
                    {
                        LogHelper.Get().Debug("---> PERF: before async call");
                    }
#endif
                    using (new OperationContextScope(Proxy.InnerChannel))
                    {
                        // Add a SOAP Header to an outgoing request
                        // For transport with message credentials security:
                        // See: https://blogs.msdn.microsoft.com/wsdevsol/2014/02/07/adding-custom-messageheader-and-http-header-to-a-wcf-method-call-in-a-windows-store-app-or-windows-phone-app/

                        if (Security != null)
                        {

                            MessageHeader aMessageHeader = MessageHeader.CreateHeader("Security",
                                "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd", Security);

                            OperationContext.Current.OutgoingMessageHeaders.Add(aMessageHeader);

                        }
                        call(Proxy);
                    }
                }
                catch (Exception ex)
                {
                    App.MobileLogger.Error("MobileSafeWcfProxy", "InvokeAsync", "Error: " + ex.ToString());
                }
            }

            public void OnCallCompleted(object sender, object eventArgs)
            {
                try
                {
#if PERFORMANCE
                    if (LogHelper.Get().IsDebug)
                    {
                        LogHelper.Get().Debug("---> PERF: after sync call (eventargs type is " + eventArgs?.GetType().Name + ")");
                    }
#endif
                    if (eventArgs != null && eventArgs is System.ComponentModel.AsyncCompletedEventArgs && (((System.ComponentModel.AsyncCompletedEventArgs)eventArgs).Error != null))
                    {
                        // We have a low-level WCF exception. Log it:
                        App.MobileLogger.Error("MobileSafeWcfProxy", "SafeWcfProxy.OnCallCompleted", "Error: received a WCF exception: " + ((System.ComponentModel.AsyncCompletedEventArgs)eventArgs).Error.ToString());
                    }

                    onCompletedAction?.Invoke(sender, eventArgs);
                }
                catch (Exception ex)
                {
                    App.MobileLogger.Error("MobileSafeWcfProxy", "OnCallCompleted", "Error: " + ex.ToString());
                }
                finally
                {
                    Close();
                }
            }
        }
    }

    public class ShopServiceProxy : MobileSafeWcfProxy<WCFShop.FtShopServiceClient, WCFShop.IFtShopService>
    {

        public ShopServiceProxy(Func<WCFShop.FtShopServiceClient> createClient)
            : base(createClient)
        {
        }

        public override ProxyWrapper<WCFShop.FtShopServiceClient> Instance
        {
            get
            {
                ProxyWrapper<WCFShop.FtShopServiceClient> proxy = new ProxyWrapper<WCFShop.FtShopServiceClient>(createClient());
                return proxy;
            }
        }

    }

    public class UserServiceProxy : MobileSafeWcfProxy<WCFUser.FtUserServiceClient, WCFUser.IFtUserService>
    {
        public UserServiceProxy(Func<WCFUser.FtUserServiceClient> createClient)
            : base(createClient)
        {

        }

        public override ProxyWrapper<WCFUser.FtUserServiceClient> Instance
        {
            get
            {
                ProxyWrapper<WCFUser.FtUserServiceClient> proxy = new ProxyWrapper<WCFUser.FtUserServiceClient>(createClient());
                return proxy;
            }
        }
    }
}

---------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;

namespace MobileCommon
{
    /// <summary>
    /// Serializes into this XML:
    /// Note that XML Namespace wsse = http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd
    /// <wsse:Security>
    ///     <wsse:UsernameToken>
    ///         <wsse:Username>XXX</wsse:Username>
    ///         <wsse:Password>YYY</wsse:Password>
    ///     </wsse:UsernameToken>
    /// </wsse:Security>
    /// </summary>
    [DataContract(Name = "Security", Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
    public class WcfSecurity
    {
        [DataMember(Name = "UsernameToken")]
        public WcfUsernameToken UsernameToken { get; set; }
    }

    [DataContract(Name = "UsernameToken", Namespace = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd")]
    public class WcfUsernameToken
    {
        [DataMember(Name = "Username")]
        public string Username { get; set; }

        [DataMember(Name = "Password")]
        public string Password { get; set; }
    }

}


Hope this helps.
Best regards
Christophe