We have the following setup:
We have written a CSLA 3.8.0 library and would like to run it remotely via WCF. We would like to set up the following scenarios:
We've really been beating our heads against the wall trying to figure out what we need to put in our config files to make this happen though. When we make the call to our WCF service, it seems to lose the credentials when it tries to connect to the database. Both servers trust each other for delegation.
If somebody could provide me with a very simple example of how to set up those two authentication scenarios, I would greatly appreciate it.
If not an example, I'd like to at least know if anybody out there has successfully implemented this. The goal on our end is to be able to do all of this through configuration files, so that we can switch between running locally versus remotely without having to implement code changes.
I feel like I'm getting close but I'm not quite there. I have a hard time believing nobody has attempted this before.
So far I have added this to the service's web.config file:
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceAuthorization impersonateCallerForAllOperations="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="ServiceBehavior" name="Csla.Server.Hosts.WcfPortal">
<endpoint binding="wsHttpBinding"
contract="Csla.Server.Hosts.IWcfPortal" />
</service>
</services>
</system.serviceModel>
I also added this to the client's app.config:
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="ImpersonationBehavior">
<clientCredentials>
<windows allowedImpersonationLevel="Impersonation" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint name="WcfDataPortal" behaviorConfiguration="ImpersonationBehavior"
address="http://mywebserver/DocumentManagementHost/WcfService.svc"
binding="wsHttpBinding"
contract="Csla.Server.Hosts.IWcfPortal" />
</client>
</system.serviceModel>
What I'm getting back from the server's event log is the following:
WebHost failed to process a request.
Sender Information: System.ServiceModel.ServiceHostingEnvironment+HostingManager/2685261
Exception: System.ServiceModel.ServiceActivationException: The service '/DocumentManagementHost/WcfService.svc' cannot be activated due to an exception during compilation. The exception message is: The service operation 'Create' that belongs to the contract with the 'IWcfPortal' name and the 'http://ws.lhotka.net/WcfDataPortal' namespace does not allow impersonation.. ---> System.InvalidOperationException: The service operation 'Create' that belongs to the contract with the 'IWcfPortal' name and the 'http://ws.lhotka.net/WcfDataPortal' namespace does not allow impersonation.
at System.ServiceModel.Dispatcher.SecurityValidationBehavior.WindowsIdentitySupportRule.Validate(ServiceDescription description)
at System.ServiceModel.Dispatcher.SecurityValidationBehavior.System.ServiceModel.Description.IServiceBehavior.Validate(ServiceDescription description, ServiceHostBase serviceHostBase)
at System.ServiceModel.Description.DispatcherBuilder.ValidateDescription(ServiceDescription description, ServiceHostBase serviceHost)
at System.ServiceModel.Description.DispatcherBuilder.InitializeServiceHost(ServiceDescription description, ServiceHostBase serviceHost)
at System.ServiceModel.ServiceHostBase.InitializeRuntime()
at System.ServiceModel.ServiceHostBase.OnOpen(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open(TimeSpan timeout)
at System.ServiceModel.Channels.CommunicationObject.Open()
at System.ServiceModel.ServiceHostingEnvironment.HostingManager.ActivateService(String normalizedVirtualPath)
at System.ServiceModel.ServiceHostingEnvironment.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath)
--- End of inner exception stack trace ---
at System.ServiceModel.ServiceHostingEnvironment.HostingManager.EnsureServiceAvailable(String normalizedVirtualPath)
at System.ServiceModel.ServiceHostingEnvironment.EnsureServiceAvailableFast(String relativeVirtualPath)
Process Name: w3wp
Process ID: 8500
For more information, see Help and Support Center at http://go.microsoft.com/fwlink/events.asp.
It looks like if I can allow impersonation for WcfDataPortal somehow, I may be OK. I'm just not sure how to do that. I suspect it has something to do with adding [OperationBehavior(Impersonation = ImpersonationOption.Required)] to the service methods.
I'm not sure what sort of settings I would have to use to impersonate a different user as well.
You may also need to subclass WcfProxy to configure the underlying WCF proxy object with your client Windows credentials. I don't know if that's required, but I do know we enhanced WcfProxy a long time ago so it was easy to subclass for the purpose of setting things like credentials on the actual WCF objects.
Thank you for the reply Rocky. I feel like I'm getting much closer to the answer. I implemented my own custom WcfProxy in my client app. I tried setting some different values of the ChannelFactory but I kept getting the error I pasted above. I may not be setting all that I need to be setting though. There appears to be some provision for logging in as a specific user as well. I pasted the basic code for the proxy below.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Principal;
using System.ServiceModel;
using System.ServiceModel.Security;
namespace DocumentManagementUI
{
public class WcfProxy : Csla.DataPortalClient.WcfProxy
{
protected override ChannelFactory<Csla.Server.Hosts.IWcfPortal> GetChannelFactory()
{
var f = base.GetChannelFactory();
return f;
}
}
}
All I need to figure out now is what goes before "return f".
Have you read through the WCF security guidance book from Patterns and Practices?
http://www.codeplex.com/WCFSecurity/
That's probably the most comprehensive source for WCF security configuration available.
Thank you Rocky that link was very helpful. I believe I have put all of the pieces together. Hopefully this thread will be helpful for anybody attempting this in the future.
First and foremost, I had to make a modification to CSLA to make this happen. I would like to request this as an enhancement to the framework. I had to go into the WcfPortal class and decorate all of the methods with the following attribute:
[OperationBehavior(Impersonation = ImpersonationOption.Allowed)]
I confirmed that my configuration would not work without this attribute being defined. I would hope that this could be added to CSLA without causing any unwanted side effects.
Below I have pasted the complete web.config file for the service (which is hosted in IIS). This is essentially the same as what I pasted earlier. In IIS, the web application is configured to allow anonymous as well as windows integrated authentication. WCF has its own authentication mechanism. If you don't allow anonymous in IIS, WCF will give you an error.
<?xml version="1.0"?>
<configuration>
<appSettings>
<add key="CslaAuthentication" value="Windows"/>
</appSettings>
<connectionStrings>
<add name="DocumentManagement" connectionString="Data Source=mytestserver;Initial Catalog=DocumentManagement;Integrated Security=True" providerName="System.Data.SqlClient"/>
<add name="Biennium" connectionString="Data Source=mytestserver;Initial Catalog=Atlas;Integrated Security=True" providerName="System.Data.SqlClient"/>
</connectionStrings>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceAuthorization impersonateCallerForAllOperations="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="ServiceBehavior" name="Csla.Server.Hosts.WcfPortal">
<endpoint binding="wsHttpBinding"
contract="Csla.Server.Hosts.IWcfPortal" />
</service>
</services>
</system.serviceModel>
</configuration>
It's up to the client to determine which identity to send to the service. In the first scenario, I wanted to impersonate the calling user. To do that, this is how I had to configure the client's app.config. Note that I had to adjust the maxReceivedMessageSize or I'd get an error about the message being too big. The kicker here is that I had to set allowedImpersonationLevel to Delegation, not Impersonation.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<appSettings>
<add key="CslaAuthentication" value="Windows"/>
<add key="CslaDataPortalProxy" value="Csla.DataPortalClient.WcfProxy, Csla"/>
</appSettings>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="wsHttpBinding_Limits"
maxReceivedMessageSize="2147483647">
</binding>
</wsHttpBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="ImpersonationBehavior">
<clientCredentials>
<windows allowedImpersonationLevel="Delegation" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint name="WcfDataPortal" behaviorConfiguration="ImpersonationBehavior"
address="http://mywebserver/DocumentManagementHost/WcfService.svc"
binding="wsHttpBinding"
bindingConfiguration="wsHttpBinding_Limits"
contract="Csla.Server.Hosts.IWcfPortal" />
</client>
</system.serviceModel>
</configuration>
Next I wanted to test impersonating a specific user. To do this, I had to subclass my own WcfProxy class in the client application. Here's what that code looks like:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Principal;
using System.ServiceModel;
using System.ServiceModel.Security;
namespace DocumentManagementUI
{
public class WcfProxy : Csla.DataPortalClient.WcfProxy
{
protected override ChannelFactory<Csla.Server.Hosts.IWcfPortal> GetChannelFactory()
{
var f = base.GetChannelFactory();
f.Credentials.Windows.ClientCredential = new System.Net.NetworkCredential("userName", "password", "domain");
return f;
}
}
}
After that was done, I had to go back into the client's app.config and modify the CslaDataPortalProxy value to look like this:
<add key="CslaDataPortalProxy" value="DocumentManagementUI.WcfProxy, DocumentManagement"/>
That's about it. Both scenarios are taken care of using this method. Hopefully my plea to get those attributes added to those CSLA methods goes through so I don't have to keep modifying the library :).
I tried the suggestion of adding the attribute tags as suggested and it works, thanks for posting. Has this been addressed in later releases, or are we supposed to modify the framework?
I don't think modifying the framework is required, because you can create your own subclass of WcfPortal and use that instead.
Copyright (c) Marimer LLC