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)

See Also:
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

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

Note You need to log in before you can comment on or make changes to this bug.