i have created service with such binding configuration:
<bindings>
<customBinding>
<binding name="DefaultBinding">
<textMessageEncoding messageVersion="Soap12" />
<httpTransport />
</binding>
</customBinding>
</bindings>
And when my service receives message starting like this:
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
<s:Header>
<Security s:mustUnderstand="1" xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<UsernameToken>
<Username>
</Username>
<Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">...</Password>
<Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">KWVa4abCrEemOMT55VEZkgIAAAAAAA==</Nonce>
<Created xmlns="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">2013-08-28T13:29:05.966Z</Created>
</UsernameToken>
</Security>
...
It produces error:
The header 'Security' from the namespace 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd' was not understood ...
I also tried:
<wsHttpBinding>
<binding name="DefaultBinding">
<security mode="Message" />
</binding>
</wsHttpBinding>
How i can process this header or ignore it ?
Update
As i understood i need username over insecure transport, so i tried:
<customBinding>
<binding
name="DefaultBinding">
<textMessageEncoding messageVersion="Soap12" />
<security authenticationMode="UserNameOverTransport" allowInsecureTransport="True">
</security>
<httpTransport>
</httpTransport>
</binding>
</customBinding>
I also tried CUB:
<bindings>
<clearUsernameBinding>
<binding name="myClearUsernameBinding" messageVersion="Soap12">
</binding>
</clearUsernameBinding>
</bindings>
Both ends with error on client: An error occurred when verifying security for message. But it works with test CUB's client. What could be wrong ?
CUB's envelope's header.
Test client's header.
Solution was simple:
Create service behavior
Create dispatch message inspector
Add created service behavior to server
And then just parse or just delete unused "mustUnderstand" headers.
Step 1:
public class WSSecurityBehavior : IServiceBehavior {
public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) {
}
public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase,
Collection<ServiceEndpoint> endpoints,
BindingParameterCollection bindingParameters) {
}
public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) {
var endpoints = serviceHostBase
.ChannelDispatchers
.Cast<ChannelDispatcher>()
.SelectMany(dispatcher => dispatcher.Endpoints);
foreach (var endpoint in endpoints)
endpoint.DispatchRuntime.MessageInspectors.Add(new WSSecurityInspector());
}
}
Step 2:
public class WSSecurityInspector : IDispatchMessageInspector {
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) {
var headerPosition = request.Headers.FindHeader("Security",
"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd");
if (headerPosition > -1)
request.Headers.RemoveAt(headerPosition);
return null;
}
public void BeforeSendReply(ref Message reply, object correlationState) {
}
}
Step 3:
Host.Description.Behaviors.Add(new WSSecurityBehavior());
You've got a mismatch in the soap structure/contract you're exchanging between the client and host. The structure of the messages exchanged between the client and host (including the namespace) must match exactly. If they don't, you get this error.
What you want to do is get Fiddler running on your host. Then, running Fiddler as an man-in-the-middle proxy, resend the request from your client machine. When the request/response is finished, examine the messages in Fiddler, particularlly the namespace and structure differences between the request and response and there you should find the problem.
The client is trying to authenticate to your server using user/pass in the message level. You need to decide if this is expected behavior, in which case you need to configure your service to do username authentication. This is not just a matter of setting a binding, you need to decide how you authenticate users (e.g. via a database, windows credentials etc). Once you know that you should be able to use a binding like this:
<customBinding>
<binding name="NewBinding0">
<textMessageEncoding />
<security authenticationMode="UserNameOverTransport">
<secureConversationBootstrap />
</security>
<httpsTransport />
</binding>
</customBinding>
I cannot be sure that this is what you need since you did not publish the full soap envelope. You might need to set messageVersion attribute on the text encoding element, or use CUB in case there is no SSL.
Then you need to configure how you are going to verify the username. One example is here.
you can use basicHttpBinding, download the project and sample code from this post for password digest in wcf PasswordDigest Auth this is my configuration with this implementation (i edited some code for custom needs but this solution works fine).
Create a binding:
<bindings>
<basicHttpBinding>
<binding name="securityBinding">
<security mode="TransportWithMessageCredential">
<message clientCredentialType="UserName"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
And after set a behavior
<behavior name="securityBehavior">
<DataMessageTracer/>
<serviceCredentials type="WCF.SecurityExtensions.ServiceCredentialsEx, WCF.SecurityExtensions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null">
<serviceCertificate findValue="hypori2.zed.pa" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName"/>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="MyService.Utils.PassValidator, MyService, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</serviceCredentials>
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
Related
I'm trying to create a callback in WCF service. Service so far was using basicHttpBinding, so I want to add another end point for netTcpBinding. Service is already hosted in IIS. First It was hosted in IIS 6, but then I installed IIS 7.
So, I'm getting the following error:
The requested service, 'net.tcp://localhost:1801/MyServiceName.svc/NetTcpExampleAddress' could not be activated. See the server's diagnostic trace logs for more information.
When seeing the log, this is the message:
So the main error is:
Contract requires Duplex, but Binding 'BasicHttpBinding' doesn't support it or isn't configured properly to support it.
Here are my config files:
My Web.config for the server:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="demoServiceNetTcpBinding">
<security mode="None"/>
</binding>
</netTcpBinding>
<basicHttpBinding>
<binding name="demoServiceHttpBinding" receiveTimeout="00:05:00" sendTimeout="00:05:00" maxReceivedMessageSize="2147483647">
<security mode="None"/>
</binding>
</basicHttpBinding>
</bindings>
<services>
<service name="MyServerName.MyServiceName">
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:1801/MyServiceName.svc/"/>
<add baseAddress="http://localhost:1800/MyServiceName.svc/"/>
</baseAddresses>
</host>
<endpoint
address="NetTcpExampleAddress"
binding="netTcpBinding"
bindingConfiguration="demoServiceNetTcpBinding"
contract="MyServerName.SharedContract.IMyServiceName"/>
<endpoint
address="BasicHttpExampleAddress"
binding="basicHttpBinding"
bindingConfiguration="demoServiceHttpBinding"
contract="MyServerName.SharedContract.IMyServiceName"/>
<endpoint address="mex" binding="mexTcpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceDebug includeExceptionDetailInFaults="true"/>
<serviceMetadata httpGetEnabled="true"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
My App.config for the client:
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="demoServiceNetTcpBinding">
<security mode="None"/>
</binding>
</netTcpBinding>
<basicHttpBinding>
<binding name="demoServiceHttpBinding" receiveTimeout="00:05:00" sendTimeout="00:05:00" maxReceivedMessageSize="2147483647">
<security mode="None"/>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint name="NetTcpExampleName"
address="net.tcp://localhost:1801/DicomQueryService.svc/NetTcpExampleAddress"
bindingConfiguration ="demoServiceNetTcpBinding"
contract="MyServerName.SharedContract.IMyServiceName"
binding="netTcpBinding" />
<endpoint name="BasicHttpExampleName"
address="http://localhost:1800/MyServiceName.svc/BasicHttpExampleAddress"
bindingConfiguration ="demoServiceHttpBinding"
contract="MyServerName.SharedContract.IMyServiceName"
binding="basicHttpBinding" />
</client>
</system.serviceModel>
Settings in my IIS:
If there are any other pieces of code that you need, please let me know and I'll update the question.
EDIT 1:
Here are more details from the code, of how I'm calling the service from the client (on client side):
public class MyCommandClass : IMyServiceCallback
{
public MyCommandClass()
{
var ctx = new InstanceContext(new MyCommandClass());
DuplexChannelFactory<MyServerName.SharedContract.IMyServiceName> channel = new DuplexChannelFactory<MyServerName.SharedContract.IMyServiceName>(ctx, "NetTcpExampleName");
MyServerName.SharedContract.IMyServiceName clientProxy = channel.CreateChannel();
clientProxy.MyFunction(); //debug point is comming here and then it throws the error
clientProxy.ProcessReport();
(clientProxy as IClientChannel).Close();
channel.Close();
}
public void Progress(int percentageCompleted)
{
Console.WriteLine(percentageCompleted.ToString() + " % completed");
}
}
where interfaces (on server side) are defined as:
[ServiceContract(CallbackContract = typeof(IMyServiceCallback))]
public interface IMyServiceName
{
[OperationContract]
void MyFunction();
[OperationContract(IsOneWay = true)]
void ProcessReport();
}
public interface IMyServiceCallback
{
[OperationContract(IsOneWay = true)]
void Progress(int percentageCompleted);
}
and service (on server side) is defined as:
public class MyServiceName: IMyServiceName
{
public void MyFunction()
{
//do something
}
public void ProcessReport()
{
//trigger the callback method
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100);
OperationContext.Current.GetCallbackChannel<IMyServiceCallback>().Progress(i);
}
}
}
My methods so far are just a demo. Once the error related to this question is fixed, then I'll start with developing the methods.
Your service contract requires duplex connection (you have ServiceCallback attribute). Therefore all endpoints that this service exposes must support duplex connection. Net.tcp does support it, but basicHttp does not, so you cannot use basicHttp with your service now.
I am having a problem with a WCF service using Windows authentication on one of the servers I am deploying it to (it's a Windows Server 2008 R2 machine), while it works flawlessly on all other machines I have access to (Windows 7, Windows Server 2008 and Windows Server 2008 R2). I managed to reproduce the issue with a really simple sample application which more or less completely excludes my code as the cause of the problem.
The minimum application I can reproduce the issue with is a minor modification of the WCF service project template:
[ServiceContract]
public interface IService1
{
[OperationContract]
string GetData(int value);
}
[AspNetCompatibilityRequirements(RequirementsMode=AspNetCompatibilityRequirementsMode.Allowed)]
public class Service1 : IService1
{
public string GetData(int value)
{
return string.Format("You entered: {0}\nUsername: {1}",
value,
ServiceSecurityContext.Current == null ?
"<null>" :
ServiceSecurityContext.Current.PrimaryIdentity.Name);
}
}
Basically I enabled ASP.NET compatibility (I need it because the actual code uses an HttpHandler for authentication) and the username of the authenticated user is returned.
The contents of web.config are as follows:
<?xml version="1.0"?>
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0" />
<authentication mode="Windows"/>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="HttpWindowsBinding" maxReceivedMessageSize="2147483647">
<readerQuotas maxBytesPerRead="2147483647" maxArrayLength="2147483647" maxStringContentLength="2147483647" maxNameTableCharCount="2147483647" maxDepth="2147483647"/>
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Windows" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" />
<services>
<service name="TestService.Service1" behaviorConfiguration="ServiceBehavior">
<endpoint address=""
binding="basicHttpBinding"
bindingConfiguration="HttpWindowsBinding"
contract="TestService.IService1" />
<endpoint address="problem"
binding="basicHttpBinding"
bindingConfiguration="HttpWindowsBinding"
contract="TestService.IService1" />
</service>
</services>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
Notice the two endpoints: one with the default address, the other one with a relative address. Calling the first one succeeds even on the problematic server, while the call to the second one fails with following error:
Exception type: HttpException
Exception message: Failed to Execute URL.
at System.Web.Hosting.ISAPIWorkerRequestInProcForIIS6.BeginExecuteUrl(String url, String method, String childHeaders, Boolean sendHeaders, Boolean addUserIndo, IntPtr token, String name, String authType, Byte[] entity, AsyncCallback cb, Object state)
at System.Web.HttpResponse.BeginExecuteUrlForEntireResponse(String pathOverride, NameValueCollection requestHeaders, AsyncCallback cb, Object state)
at System.Web.DefaultHttpHandler.BeginProcessRequest(HttpContext context, AsyncCallback callback, Object state)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
The call only fails when the classic pipeline is used (I need it because of the HttpHandler, but the issue can be reproduced even without it). With integrated pipeline the problem is gone. Also if I disable Windows authentication, the problem is gone as well:
<binding name="HttpBinding" maxReceivedMessageSize="2147483647">
<readerQuotas maxBytesPerRead="2147483647" maxArrayLength="2147483647" maxStringContentLength="2147483647" maxNameTableCharCount="2147483647" maxDepth="2147483647"/>
<security mode="None">
<transport clientCredentialType="None" />
</security>
</binding>
I have noticed another detail with an HttpHandler registered. The value of HttpRequest.CurrentExecutionFilePath property for the endpoint with the relative address differs between the problematic server (~/Service1.svc/problem) and the working servers (~/Service1.svc). Although I'm not that well familiar with IIS, I suspect this could hint at the cause of the problem - maybe something related to the routing of requests?
I am running out of ideas therefore I'm posting this here in the hope the someone will recognize what the problem could be. Any suggestion are welcome.
Do you have URL rewriting on IIS turned on? This smells like a permission issue of some sort. What is the difference between Classic and Integrated pipeline mode in IIS7? Might be helpful.
The problem may be the address "~/Service1.svc/problem"
When the address is "~/Service1.svc" the call hits the svc file, and uses the information in the file to find the interface and then the configuration for that interface.
When you use a relative address without a svc file, it looks at the address in the config file.
Do you have a directory "Service1.svc" on one of the servers, or is the address without the ".svc" on the server where it works?
I've read a lot of posts about the problem to use WP7 + WCF (IIS 7) over HTTPS with basic authentication but I'm still in trouble with it...
If I just use HTTPS transport without BasicAuth it works like a charm. But both combinated doesn't work for me...
Maybe you can help me to identify my failure...
My ClientConfig:
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="BasicHttpBinding_IService1" maxBufferSize="2147483647"
maxReceivedMessageSize="2147483647">
<security mode="Transport" />
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint
binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1"
contract="ServiceReference1.IService1" name="BasicHttpBinding_IService1" />
</client>
</system.serviceModel>
My ServiceConfig:
<?xml version="1.0"?>
<configuration>
<appSettings/>
<system.web>
<compilation debug="true" targetFramework="4.0"/>
<pages controlRenderingCompatibilityVersion="3.5" clientIDMode="AutoID"/>
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
<serviceCredentials>
<userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WP7.CustomUserNameValidator, WP7" />
</serviceCredentials>
</behavior>
</serviceBehaviors>
</behaviors>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
<bindings>
<basicHttpBinding>
<binding maxReceivedMessageSize="2147483647">
<readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
<security mode="TransportWithMessageCredential" >
<transport clientCredentialType="Basic"/>
<message clientCredentialType="UserName"/>
</security>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
My CustomUserNameValidator used in Service:
namespace WP7
{
public class CustomUserNameValidator : UserNamePasswordValidator
{
// This method validates users. It allows in two users,
// test1 and test2 with passwords 1tset and 2tset respectively.
// This code is for illustration purposes only and
// MUST NOT be used in a production environment because it
// is NOT secure.
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
if (!(userName == "test1" && password == "1tset") && !(userName == "test2" && password == "2tset"))
{
throw new FaultException("Unknown Username or Incorrect Password");
}
}
}
}
The code in my Wp7 App to call a method a sync (with a solution from: http://cisforcoder.wordpress.com/2010/12/01/how-to-implement-basic-http-authentication-in-wcf-on-windows-phone-7/#comment-174):
proxy = new ServiceReference1.Service1Client();
proxy.Endpoint.Address = new System.ServiceModel.EndpointAddress(new Uri(Details.mySettings.EndpointAddress));
proxy.PingServerCompleted += new EventHandler<ServiceReference1.PingServerCompletedEventArgs>(proxy_PingServerCompleted);
var credentials = EncodeBasicAuthenticationCredentials("test1", "1tset");
using (OperationContextScope scope =
new OperationContextScope(proxy.InnerChannel))
{
HttpRequestMessageProperty request = new HttpRequestMessageProperty();
request.Headers[System.Net.HttpRequestHeader.Authorization] = "Basic " + credentials;
OperationContext.Current.OutgoingMessageProperties.Add(
HttpRequestMessageProperty.Name, request);
proxy.PingServerAsync(myServer);
}
private string EncodeBasicAuthenticationCredentials(string username, string password)
{
//first concatenate the user name and password, separated with :
string credentials = username + ":" + password;
//Http uses ascii character encoding, WP7 doesn’t include
// support for ascii encoding but it is easy enough to convert
// since the first 128 characters of unicode are equivalent to ascii.
// Any characters over 128 can’t be expressed in ascii so are replaced
// by ?
var asciiCredentials = (from c in credentials
select c <= 0x7f ? (byte)c : (byte)'?').ToArray();
//finally Base64 encode the result
return Convert.ToBase64String(asciiCredentials);
}
Furthermore I've already set "Basic Authentication" setting in the IIS Virtual Directory to "Enabled".
Everytime I've got some different error exceptions:
either CommunicationException or SecurityException or what else...
Someone an idea which might be solve my problem?
Thanks.
Jason
The client needs to also specify the clientCredentialType - that's missing from the client config. So the client isn't expecting to have to send credentials but the service is expecting them
Sorry for asking a question about something I don't know much about, but I've been pulling my hair out trying to get this working.
So, I have a WCF service that is hosted on IIS and seems to be working insomuch that I can "see" it on the network by going to http://servername/MyService.svc in a browser.
That .svc looks like:
<% #ServiceHost Service="Foo.Bar" %>
The relevant code looks like:
[ServiceContract(Namespace = "http://schemas.microsoft.com/TeamFoundation/2005/06Services/Notification/03")]
public interface IBar
{
[OperationContract(Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify", ReplyAction = "*")]
[XmlSerializerFormat(Style = OperationFormatStyle.Document)]
void Notify(string eventXml, string tfsIdentityXml);
}
and:
public class Bar : IBar
{
public void Notify(string eventXml, string tfsIdentityXml)
{
// Just some test output to see if it worked
var path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "tfs.txt");
File.WriteAllText(path, tfsIdentityXml + eventXml);
}
}
That's all been built and the ensuing .dll put into the bin dir in the site root in IIS.
I now want to subscribe via bissubscribe.exe (or a similar method) to TFS check-in events. I tried doing something like:
bissubscribe /eventType CheckinEvent
/address http://servername/MyService.svc
/deliveryType Soap
/server mytfsserver
But nothing; it doesn't even look like there was log activity. So keeping in mind I know nothing about WCF, what am I doing wrong? I imagine the address param is one thing; am I not supposed to point it to the .svc?
I have created a blog post how you can use WCF in combination with the Event Services of TFS: http://www.ewaldhofman.nl/post/2010/08/02/How-to-use-WCF-to-subscribe-to-the-TFS-2010-Event-Service-rolling-up-hours.aspx
TFS 2010 and WCF 4.0 configurations are described below...
Method signature:
public void Notify(string eventXml) /* No SubscriptionInfo! */
Web config:
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.0">
<assemblies>
<add assembly="Microsoft.TeamFoundation, Version=10.0.0.0, Culture=neutral, PublicKeyToken=B03F5F7F11D50A3A"/>
</assemblies>
</compilation>
</system.web>
<system.serviceModel>
<services>
<service behaviorConfiguration="NotificationServiceBehavior" name="TF.CheckinListener.CheckinListener">
<endpoint address="Notify" binding="wsHttpBinding" bindingConfiguration="noSecurity" contract="TF.CheckinListener.ICheckinListener" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior name="NotificationServiceBehavior">
<serviceMetadata httpGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
</binding>
<wsHttpBinding>
<binding name="noSecurity" maxBufferPoolSize="20000000" maxReceivedMessageSize="200000000">
<readerQuotas maxStringContentLength="200000000" maxArrayLength="200000000" />
<security mode="None" />
</bindings>
<serviceHostingEnvironment multipleSiteBindingsEnabled="true"/>
</system.serviceModel>
<system.webServer>
<modules runAllManagedModulesForAllRequests="true"/>
</system.webServer>
</configuration>
Subscription address for bissubscribe:
http://MachineName/VirtualDirectoryName/Service.svc/Notify
One point that jumps out is the fact you have a method that doesn't return anything except void. Those should be marked as "one-way" method in WCF:
[ServiceContract(Namespace = "http://schemas.microsoft.com/TeamFoundation/2005/06Services/Notification/03")]
public interface IBar
{
[OperationContract(Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify", ReplyAction = "*", IsOneWay=true)]
[XmlSerializerFormat(Style = OperationFormatStyle.Document)]
void Notify(string eventXml, string tfsIdentityXml);
}
Add the "IsOneWay=true" to your [OperationContract] attribute.
Other than that, there's nothing obviously wrong in your code, but to really tell, we'd need a lot more config info to really tell. Try the IsOneWay=true first and see if that solves your issue.
How is your service configured? In particular, is it configured to use basicHttpBinding?
Try creating a client to call your service to make sure it can be called.
Then, see if there's an example service from the TFS SDK - see if you can get the example to work.
I was able to complete this connection with the following:
[ServiceContract(Namespace = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03")]
public interface ITeamSystemObserver : IObservable
{
[OperationContract( Action = "http://schemas.microsoft.com/TeamFoundation/2005/06/Services/Notification/03/Notify", ReplyAction = "*" )]
[XmlSerializerFormat(Style=OperationFormatStyle.Document)]
void Notify(string eventXml, string tfsIdentityXml, SubscriptionInfo SubscriptionInfo);
}
Note you are missing the SubscriptionInfo parameter. Here is my web.config:
<basicHttpBinding>
<binding name="TfsEventServiceBasic">
<security mode="TransportCredentialOnly">
<transport clientCredentialType="Ntlm" />
</security>
</binding>
</basicHttpBinding>
So I've basically got everything up and running with wsHttpBindings and my WCF service using custom authentication over HTTPS.
The issue I'm having is with the customUserNamePasswordValidatorType:
<serviceCredentials>
<!-- Use our own custom validation -->
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="CustomValidator.CustomUserNameValidator, CustomValidator"/>
</serviceCredentials>
Following directions found here I've created my custom class as well:
namespace CustomValidator
{
public class CustomUserNameValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
if (!AuthenticateUser(userName, password))
throw new SecurityTokenValidationException("Invalid Credentials");
The error is "Could not load file or assembly 'CustomValidator' or one of its dependencies. The system cannot find the file specified.", and refers to the tail end of customUserNamePasswordValidatorType - "..., CustomValidator".
I didn't think it was a problem having my custom validator in its own namespace and class, but I can't see what else to do to make this work.
I've tried with/without the namespace at the beginning, swapping, etc - nothing.
Hoping another pair of eyes can pick this out.
Thanks.
EDIT
system.serviceModel
<system.serviceModel>
<bindings>
<!-- wsHttpBinding -->
<wsHttpBinding>
<binding name="wsHttpEndpointBinding">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" />
<message clientCredentialType="UserName" />
</security>
</binding>
</wsHttpBinding>
<!-- webHttpBinding -->
<webHttpBinding>
<binding name="wsHttps" >
<security mode="Transport"/>
</binding>
</webHttpBinding>
<!-- Basic binding -->
<basicHttpBinding>
<binding name="TransportSecurity">
<security mode="Transport">
<message clientCredentialType="UserName"/>
<!-- transport clientCredentialType="None"/-->
</security>
</binding>
</basicHttpBinding>
<!-- customBinding>
<binding name="WebHttpBinding_IService">
textMessageEncoding maxReadPoolSize="64" maxWritePoolSize="16"
messageVersion="Soap12" writeEncoding="utf-8">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
</textMessageEncoding>
<httpsTransport manualAddressing="false"/>
</binding>
</customBinding -->
<!-- Another custom binding -->
<customBinding>
<binding name="CustomMapper">
<webMessageEncoding webContentTypeMapperType=
"IndexingService.CustomContentTypeMapper, IndexingService" />
<httpTransport manualAddressing="true" />
</binding>
</customBinding>
</bindings>
<serviceHostingEnvironment aspNetCompatibilityEnabled="false" />
<services>
<service behaviorConfiguration="ServiceBehavior" name="Service">
<!-- Service Endpoints -->
<!-- since we're hosting in IIS, baseAddress is not required
<host>
<baseAddresses>
<add baseAddress="https://mysslserver.com/Service.svc"/>
</baseAddresses>
</host>
-->
<endpoint address="https://mysslserver.com/Service.svc"
binding="wsHttpBinding"
bindingConfiguration="wsHttpEndpointBinding"
contract="IService"
name="wsHttpEndpoint">
<!--
Upon deployment, the following identity element should be removed or replaced to reflect the
identity under which the deployed service runs. If removed, WCF will infer an appropriate identity
automatically.
-->
<!--identity>
<dns value="https://mysslserver.com"/>
</identity-->
</endpoint>
<!-- endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/ -->
</service>
</services>
<behaviors>
<endpointBehaviors>
<behavior name="webBehavior">
<webHttp />
</behavior>
</endpointBehaviors>
<serviceBehaviors>
<behavior name="ServiceBehavior">
<!-- Setup Security/Error Auditing -->
<serviceSecurityAudit auditLogLocation="Application"
suppressAuditFailure="false"
serviceAuthorizationAuditLevel="Failure"
messageAuthenticationAuditLevel="Failure" />
<serviceMetadata httpGetEnabled="false" httpsGetEnabled="true"
httpsGetUrl="https://mysslserver.com/Service.svc"/>
<serviceDebug includeExceptionDetailInFaults="false" />
<serviceCredentials>
<!-- Use our own custom validation -->
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="CustomValidator.CustomUserNameValidator, CustomValidator"/>
</serviceCredentials>
</behavior>
</serviceBehaviors>
<!-- serviceBehaviors>
<behavior name="ServiceBehavior">
<serviceMetadata httpsGetEnabled="true"
httpsGetUrl="https://mysslserver.com/Service.svc" />
To receive exception details in faults for debugging purposes, set the value below to true. Set to false before deployment to avoid disclosing exception information
<serviceDebug includeExceptionDetailInFaults="true"/>
</behavior-->
</behaviors>
</system.serviceModel>
I decided to give it another stab, and didn't like having my custom validator in another lib.
So I created a new class in App_Code, and went at it...
The following is what actually fixed it,
="CustomValidator.CustomUserNameValidator, App_Code"
When you refer to the custom validator with the values
="CustomValidator.CustomUserNameValidator, CustomValidator"
The first value is the type name and the second is the name of the assembly
in which to find the type. So I would suggest that in your first instance
your service is actually in some other assembly such as MyService
In that case you really needed your config file to say
="CustomValidator.CustomUserNameValidator, MyService"
I suspect that when you have created your new class library for your
validator, you have called your project CustomValidator (which will
output an assembly called CustomValidator.dll), and hence now your
config will work (i.e. it has nothing to do with being in a separate
class library - it just happens that the naming of your assembly
reference in the web.config is now valid)
Seems a bit strange, but the solution was to create a separate class library and make reference to its DLL in my WCF service.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;
using System.ServiceModel;
/// <summary>
/// Summary description for CustomUsernamePasswordValidator
/// </summary>
namespace CustomValidator
{
public class CustomUserNameValidator : UserNamePasswordValidator
{
public override void Validate(string userName, string password)
{
if (null == userName || null == password)
{
throw new ArgumentNullException();
}
if (!AuthenticateUser(userName, password))
throw new SecurityTokenValidationException("Invalid Credentials");
else
{
// do nothing - they're good
}
}
public bool AuthenticateUser(string userName, string password)
{
if (userName != "userbill" || password != "passwordbill")
{
return false;
}
else
{
return true;
}
}
}
}
I then made added a reference to System.IdentityModel and System.ServiceModel.
The serviceCredentials section for the WCF service is now changed to this:
<serviceCredentials>
<!-- Use our own custom validation -->
<userNameAuthentication userNamePasswordValidationMode="Custom"
customUserNamePasswordValidatorType="CustomValidator.CustomUserNameValidator, CustomValidator"/>
</serviceCredentials>
Hope that helps someone.
I tried this with invalid credentials, and was expecting to see my "Invalid Credentials" message. Instead I'm getting "At least one security token in the message could not be validated."
Other than that this thing is finally up and running!
Just reading this as it was helpful for a POC I had to get going quickly. In response to ELHaix above...this should work to ensure your descriptive custom error is returned back to the client:
using System.ServiceModel
...
throw new FaultException("Invalid Credentials - Custom Error");