Thursday, April 29, 2010

WCF, wsHttpBinding and userName authentication

Ok, before we begin here is what you need: a website with SSL properly configured using a certificate with a root certificate. You need to install the root certificate on the client from which you are going to be accessing the web-service.

SSL is required because WCF will not allow you to send username/password over an insecure channel. WCF is stringent about this and hence it doesnt even allow a plain self-signed certificate. The cert needs to have a root that is part of the trusted authority certs.

Without a root certificate, you will get the following error:

Could not establish trust relationship for the SSL/TLS secure channel with authority 'fullyQualifiedDomainName'.

For instructions on creating a website cert that has a root cert, see my previous post: Using MakeCert to create a certificate with a trusted root for an IIS website

1. Create a WCF Service website

image

2. This will create IService.cs (the service contract), Service.cs (the service implementation) and Service.svc (the webservice access point). We will be using the default implementation to for this tutorial.

3. Create the UserNameValidator class

Add a class to the app_code folder called UserNameValidator

Add the following code to the file

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.IdentityModel.Selectors;
using System.IdentityModel.Tokens;

/// <summary>
/// Summary description for UserNameValidator
/// </summary>
public class UsernameValidator : UserNamePasswordValidator
{
    public override void Validate(string userName, string password)
    {
        // validate arguments
        if (string.IsNullOrEmpty(userName))
            throw new ArgumentNullException("userName");
        if (string.IsNullOrEmpty(password))
            throw new ArgumentNullException("password");

        // check if the user is not test
        if (userName != "test" || password != "test")
            throw new SecurityTokenException("Unknown username or password");
    }
}

This code inherits from the UserNamePasswordValidator class and checks for the username “test” and password “test”.

4. First a simple test

Lets test the service using basicHttpBinding

<system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="defaultProfile">
                    <serviceMetadata httpGetEnabled="true"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <bindings>
      <basicHttpBinding>
        <binding name="basic"></binding>
      </basicHttpBinding>
        </bindings>
        <services>
            <service behaviorConfiguration="defaultProfile" name="Service">
                <endpoint address="" binding="basicHttpBinding" bindingConfiguration="basic" name="basicService" contract="IService"/>
             </service>
        </services>
    </system.serviceModel>

The above configuration enables you to access the service using basicHttpBinding.

Once you have made sure that the service works, move on to the next step. (use WcfTestClient.exe to help you testing the service – "C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\WcfTestClient.exe")

5. Convert the webservice to use wsHttpBinding without SSL.

For this you need to change your configuration as follows:

<system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="defaultProfile">
                    <serviceMetadata httpGetEnabled="true"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <bindings>
      <basicHttpBinding>
        <binding name="basic"></binding>
      </basicHttpBinding>
           
<wsHttpBinding>
                <binding name="wsPlainBinding"></binding>
            </wsHttpBinding>
        </bindings>
        <services>
            <service behaviorConfiguration="defaultProfile" name="Service">
                <endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsPlainBinding" name="wsPlainService" contract="IService"/>
            </service>
        </services>
    </system.serviceModel>

6. Migrate your site into IIS.

Visual Stuido’s web server (Casini) does not support ssl (https), so you need to move your into IIS.

Easiest way to do this is to create a virtual folder that points to your development folder.

Once that is done, test your webservice once again, this time running it from within IIS. (you will also need to change the settings in VS so that it uses IIS for debugging).

image

7. Update your configuration to use the UserNameValidator class as the custom validator

Here is the updated configuration:

<system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior name="defaultProfile">
                    <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
                   
<serviceCredentials>
                        <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="UsernameValidator, App_Code"/>
                    </serviceCredentials>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <bindings>
      <basicHttpBinding>
        <binding name="basic"></binding>
      </basicHttpBinding>
            <wsHttpBinding>
               
<binding name="wsSecureBinding">
                    <security mode="TransportWithMessageCredential">
                        <message clientCredentialType="UserName"/>
                    </security>
                </binding>
        <binding name="wsPlainBinding"></binding>
      </wsHttpBinding>
        </bindings>
        <services>
            <service behaviorConfiguration="defaultProfile" name="Service">
                <endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsSecureBinding" name="wsSecureService" contract="IService"/>
            </service>
        </services>
    </system.serviceModel>

You should be able to view your service over https.

The only way to test the service now is to create a console app with a service reference to the https endpoint for your service. The code in your client will look like this

using (ServiceReference1.ServiceClient sc = new ServiceReference1.ServiceClient())
            {
                sc.ClientCredentials.UserName.UserName = "test";
                sc.ClientCredentials.UserName.Password = "test";
                string returndata = sc.GetData(100);
            }

8. Bringing it all together

Here is a reference configuration that brings all of the above configurations together, by providing multiple end-points:

/basic – WS-1, no security
/wsPlainBinding– WS-* no authentication
/wsSecureNoUserNameBinding – WS-* – transport security, no authentication
/wsSecureService– WS-* – transport security with authentication

<system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="defaultProfile">
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
          <serviceCredentials>
            <userNameAuthentication userNamePasswordValidationMode="Custom"
              includeWindowsGroups="false" customUserNamePasswordValidatorType="UsernameValidator, App_Code" />
            <windowsAuthentication includeWindowsGroups="false" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="basic"></binding>
      </basicHttpBinding>
      <wsHttpBinding>
        <binding name="wsSecureBinding">
          <security mode="TransportWithMessageCredential">
            <transport clientCredentialType="None" />
            <message clientCredentialType="UserName" negotiateServiceCredential="false" establishSecurityContext="false"/>
          </security>
        </binding>
        <binding name="wsSecureNoUserNameBinding">
          <security mode="Transport">
                <transport clientCredentialType="None" />
                <message clientCredentialType="None" negotiateServiceCredential="false" establishSecurityContext="false" />
            </security>
        </binding>
        <binding name="wsPlainBinding">
            <security mode="None">
                <transport clientCredentialType="None" />
                <message clientCredentialType="None" negotiateServiceCredential="false" establishSecurityContext="false" />
            </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <services>
      <service behaviorConfiguration="defaultProfile" name="Service">
        <endpoint address="/basic" binding="basicHttpBinding" bindingConfiguration="basic"
          name="insecureService" contract="IService" />
        <endpoint address="/wsPlainBinding" binding="wsHttpBinding" bindingConfiguration="wsPlainBinding"
          name="wsService" contract="IService" />
        <endpoint address="/wsSecureNoUserNameBinding" binding="wsHttpBinding"
          bindingConfiguration="wsSecureNoUserNameBinding" name="wsSemiSecureService"
          contract="IService" />
        <endpoint address="/wsSecureService" binding="wsHttpBinding"
          bindingConfiguration="wsSecureBinding" name="wsSecureService"
          contract="IService" />
      </service>
    </services>
  </system.serviceModel>

Using MakeCert to create a certificate with a trusted root for an IIS website

Why? If you dont use a certificate with a trusted root, then web browsers will complain that there is something wrong with the certificate. So what you need to do is to create both the Certificate Authority certificate (the root) and then create a derived certificate that you can use on your website. You then need to install the root certificate on all the clients that will access the website. Once that is done, when the clients access your site over SSL (https), they will get the lock icon and no error messages.

The most important use of this configuration is when you need to use username authentication with WCF using wsHttpBinding. (WCF will not allow you to use this configuration over an invalidly configured SSL connection). A separate post will detail how to enable username authentication using wsHttpBinding in WCF.

Note: this should only be used for development/testing. Never for production

1. Make a Certificate Authority certificate (root certificate) that is installed to “Trusted Root Certification Authorities”

makecert -r -pe -n "CN=Raj Local Certificate Root" -ss Root -sr localMachine -a sha1 -sky signature -sv c:\certs\RajRoot.pvk c:\certs\RajRoot.cer

2. Check that the cert was installed to “Trusted Root Certification Authorities”

To do this you need the Certificates admin tool. The easy way to do this is
Start > Run > MMC
File > Add-Remove Snap-In
Click Add
Select Certificates and click Add
Select Computer Account and hit Next
Select Local Computer

The CA certificate (created in step 1) is the one that you will have to put on all the clients that will access the SSL website. (otherwise you will end up getting an error that the certificate is not trusted).

3. Create a certificate using the CA cert. This is the one that you will install to your web server

makecert -pe -n "CN=fq.dn" -a sha1 -sky exchange -eku 1.3.6.1.5.5.7.3.1 -ic c:\certs\RajRoot.cer -iv c:\certs\RajRoot.pvk -sp "Microsoft RSA SChannel Cryptographic Provider" -sy 12 -sv c:\certs\rajServer.pvk c:\certs\rajServer.cer 
pvk2pfx -pvk c:\certs\rajServer.pvk -spc c:\certs\rajServer.cer -pfx c:\certs\rajServer.pfx

In the certificates admin tool, right click > All Tasks > Import and import the pfx file that you created above.

4. Assign the certificate to your IIS website.

Right click on the website > Properties > Directory Security tab
Server Certificate
On the second page choose “Assign an existing certificate”
Select the certificate that you imported in the last step of (3).

5. Test

Try and browse to a valid page on your site. Next try and add the s to the http and try it again and make sure you are able to connect to the site.

Notes:

Using MakeCert
http://www.digitallycreated.net/Blog/38/using-makecert-to-create-certificates-for-development (a good post that details all the different options)

Creating X.509 Certificates using makecert.exe: http://blogs.microsoft.co.il/blogs/applisec/archive/2008/04/08/creating-x-509-certificates-using-makecert-exe.aspx

Wednesday, April 28, 2010

Setting up WCF Configuration using the Service Configuration Editor

1. Create a new service using basicHttpBinding

Service type: Typically the name of the class that will be implementing the Service contract (interface).
Contract: Name of the interface that represents the service contract that this service is using.
Communication mode: select HTTP if your service will be running within IIS as a website.
Interoperability: Basic web services interoperability (for BasicHttpBinding)
Address: (blank)

Once the wizard completes, click on the “Services” node under Configuration and then click on the first node under EndPoints (typically - (Empty Name)).
Provide a name for the endpoint. (basic)
Click on the “Services” node. Under the services pane, click “Click to create” for Binding Configuration.
Change the name to BasicBinding.

Add WSDL
Go to the node “Service behaviors” under advanced.
Click “New service behavior configuration” and name it BasicServiceBehavior
Click Add, and select “serviceMetadata”
Double click the added item “serviceMetaData”.
Set HttpGetEnabled to true.
Click on the “Service” node under “Services” (this is the service that you have created)
Set the behavior configuration to the one you just created.

This is what your configuration will look like

<behaviors>
   <serviceBehaviors>
    <behavior name="BasicServiceBehavior">
     <serviceMetadata httpGetEnabled="true" />
    </behavior>
   </serviceBehaviors>
  </behaviors>
  <bindings>
   <basicHttpBinding>
    <binding name="BasicBinding" />
   </basicHttpBinding>
  </bindings>
  <services>
   <service behaviorConfiguration="BasicServiceBehavior" name="Service">
    <endpoint address="" binding="basicHttpBinding" bindingConfiguration="BasicBinding"
     name="basic" contract="IService" />
   </service>
  </services>

2. Create an endpoint that uses wsHttpBinding

First – create your binding.
Right click on “Bindings” node and select new binding configuration.
Name it “wsInsecureBinding”
On the security tab (right pane), set Mode to none, EstablishSecurityContext to false, MessageClientCredentialType to None, NegotiateServiceCredential to false, TransportClientCredentialType to None.
image 
Now create the endpoint:

Right click on the EndPoints node (under Services->YourServiceName) and select new endpoint. Name it “wsInsecure”
Set the address to /wsInsecure
Set the binding to wsHttpBinding
Set the bindingConfiguration to the one you just created “wsInsecureBinding”
Set the contract to the name of the interface that is your service contract.
image

The binding configuration will now look like this (changes highlighted in bold):

<behaviors>
   <serviceBehaviors>
    <behavior name="BasicServiceBehavior">
     <serviceMetadata httpGetEnabled="true" />
    </behavior>
   </serviceBehaviors>
  </behaviors>
  <bindings>
   <basicHttpBinding>
    <binding name="BasicBinding" />
   </basicHttpBinding>
   <wsHttpBinding>
    <binding name="wsInsecureBinding">
     <security mode="None">
      <transport clientCredentialType="None" />
      <message clientCredentialType="None" negotiateServiceCredential="false"
       establishSecurityContext="false" />
     </security>
    </binding>
   </wsHttpBinding>
  </bindings>
  <services>
   <service behaviorConfiguration="BasicServiceBehavior" name="Service">
    <endpoint address="" binding="basicHttpBinding" bindingConfiguration="BasicBinding"
     name="basic" contract="IService" />
   <endpoint address="/wsInsecure" binding="wsHttpBinding" bindingConfiguration="wsInsecureBinding"
     name="wsInsecureBinding" contract="IService" />
   </service>
  </services>

WCF and SoapUI – Error importing service

If SoapUI is unable to import your webservice, check the URI. SoapUI does not like spaces in the URI.

Tuesday, April 27, 2010

TFS 2008 Build Server and VS 2010

Had to install VS 2010 on the TFS 2008 build server machine, so that I could get continuous integration with VS 2010 solutions. (We are not moving to TFS 2010 just yet as we have a dependency on Office 2003).

After having installed VS 2010 on the build server all CI builds were failing. First with the error:

Error MSB4131: The "Reason" parameter is not supported by the "GetBuildProperties" task. Verify the parameter exists on the task, and it is a gettable public instance property.

The simple work-around/hack for this is to open the “C:\Program Files\MSBuild\Microsoft\VisualStudio\TeamBuild\Microsoft.TeamFoundation.Build.targets” file and comment out the line

<Output TaskParameter="Reason" PropertyName="Reason" />

Once you do this, you will come across another error:

error MSB4018: The "CreateWorkspaceTask" task failed unexpectedly.

Turns out this is because VS 2010 modifies the Build.Targets file’s Workspace entry to the following:

<WorkspaceName Condition=" '$(WorkspaceName)'=='' " >$(COMPUTERNAME)_$(BuildDefinitionId)_$(BuildAgentId)</WorkspaceName>

TFS 2008 does not like the _$(BuildAgentId). So if you set the value to $(COMPUTERNAME)_$(BuildDefinitionId) life should get back to normal for you.

Finally, if you need to VS2010 to work with your build server, edit the “C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\tfsbuildservice.exe.config” file and set the MsBuildPath.

<add key="MSBuildPath" value="C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319\" />

VS2010, TFS 2008 and Build Server

A good post on getting VS2010 working with a TFS 2008 build server

http://blogs.msdn.com/jimlamb/archive/2009/11/03/upgrading-tfs-2008-build-definitions-to-tfs-2010.aspx

Monday, April 26, 2010

TFS 2010 and Office 2003

In short: doesnt work! – TFS 2010 requires Office 2007 for managing your project.

Here is a hack that allows you to continue working with Office 2003 with TFS 2010. It is a hack because you cannot create a new file from VS 2010. The only way to create a new excel document linked to your project is from VS 2008. In addition there are some other issues, such as no support for tree of items, etc.

Here are the steps: http://blogs.msdn.com/team_foundation/archive/2010/04/24/vs-2010-and-tfs-with-office-2003.aspx

VS 2010 Code Analysis Issue

Are you getting the following error when Code Analysis is run on a project with Visual Studio 2010?

“MSBUILD : error : CA0059 : Invalid settings passed to CodeAnalysis task”

For me this was being caused because I had run FxCop on my machine. This lead to an environment variable called “FxCopDir” being created and that pointed to the FxCop install directory. Once I deleted the FxCopDir environment variable and restarted Visual studio, Code Analysis began running properly.

To get to the environment variables, right click and select properties on “My Computer”, On the Advanced tab you will find the “Environment Variables” button. Find and delete FxCopDir variable.

Updating the TFS Warehouse cube

The TFS warehouse cube is updated on a schedule (typically hourly). But if you need to update the cube ad-hoc, here is what you do:

1. Goto http://teamserver:8080/Warehouse/v1.0/WarehouseController.asmx

2. Run the “Run” operation. You should get a return value of true

3. Run the “GetWarehouseStatus” operation to determine if the update completed (will return Idle).

The invoke operation is available only on the server where TFS is running.