Workflow First and Security Sample

Download sample

This sample demonstrates two critical features within the workflow services:

  • "Workflow first" method of authoring services

    You can author services using workflows in one of two ways. One is "contract first" authoring, where the contract already exists and the workflow implements the contract. The other is workflow first, which creates the contract as the workflow is authored. The workflow designer, in effect, acts like the contract designer. In this sample, the calculator contract is created as the workflow is authored.

    In this sample, the mark up for workflow first is implemented in the file SequentialCalculatorService.xoml. To understand the programming model behind workflow-first programming, compare this to the mark up for other workflow samples.

    The following sample code shows the mark up for the Receive activity in the sample.

    <ns0:ReceiveActivity x:Name="ReceiveAdd" OperationValidation="ValidateOwner" CanCreateInstance="True">
    <ns0:ReceiveActivity.ServiceOperationInfo>
            <ns0:OperationInfo PrincipalPermissionRole="Administrators" Name="Add" ContractName="ICalculator">
                <ns0:OperationInfo.Parameters>
                              <ns0:OperationParameterInfo Attributes="In" ParameterType="{x:Type p15:Int32}" Name="value" Position="0" 
                                       xmlns:p15="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
                                       <ns0:OperationParameterInfo Attributes="Out" ParameterType="{x:Type p15:Int32}" Name="returnValue" Position="1" 
                                       xmlns:p15="clr-namespace:System;Assembly=mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
                </ns0:OperationInfo.Parameters>
            </ns0:OperationInfo>
    </ns0:ReceiveActivity.ServiceOperationInfo>
        <ns0:ReceiveActivity.ParameterBindings>
            <WorkflowParameterBinding ParameterName="value">
                <WorkflowParameterBinding.Value>
                    <ActivityBind Name="SequentialCalculatorService" Path="inputValue" />
                </WorkflowParameterBinding.Value>
            </WorkflowParameterBinding>
            <WorkflowParameterBinding ParameterName="returnValue">
                <WorkflowParameterBinding.Value>
                    <ActivityBind Name="SequentialCalculatorService" Path="currentValue" />
                </WorkflowParameterBinding.Value>
            </WorkflowParameterBinding>
        </ns0:ReceiveActivity.ParameterBindings>
        <CodeActivity x:Name="DoAdd" ExecuteCode="Add" />
    </ns0:ReceiveActivity>
    

    The Receive activity contains no reference to an external contract type and the contract is defined within the Receive activity itself. The OperationInfo block in the Receive activity describes both the contract name and the operation name. These are literal strings that contain no reference to a type that defines the contract.

    When you run the sample, the workflow service host uses this definition to create a service description. To create a service client for this, use the ServiceModel Metadata Utility Tool (Svcutil.exe).

  • Security in workflow services

    Workflow services provides two levels of security for your service. In the first level you specify principle permission security on an operation. The service runtime checks the permission before delivering the message to the workflow. If the message does not meet the principle permission security, the message is not delivered to the workflow. The second level is the "operation validation condition," which you can assign to a Receive activity. The operation validation condition is executed when the Receive activity receives the message. If the message does not meet the operation validation condition, a fault is sent to the client, the message is deleted, and the workflow is faulted.

    The PrincipalPermissionRole attribute on the OperationInfo element specifies that only users in the Administrators group are allowed to call this operation.

    The OperationValidation attribute on the Receive activity points to the code handler to run as the validation condition. In this sample, only users in the Administrators group can invoke the first operation, because the PrincipalPermissionRole attribute is set on the first Receive activity. In this workflow, the ReceiveAdd Receive activity has the property CanCreateInstance set to true. This means that an instance of this service is created when the Add operation is called by a user in the Administrators group.

    In the subsequent Receive operations, the OperationValidation condition checks to ensure that the principal that invokes the operation is the same principal who created the instance. In this scenario, only the person who initiated the instance can send further messages. The following sample shows the code in the OperationValidation condition that performs this.

    private void ValidateOwner(object sender, OperationValidationEventArgs e)
    {
        if (string.IsNullOrEmpty(owner))
        {
            owner = ExtractCallerName(e.ClaimSets);
            e.IsValid = true;
            Console.WriteLine("Owner: " + owner);
        }
        if (owner.Equals(ExtractCallerName(e.ClaimSets)))
            e.IsValid = true;
    }
    private string ExtractCallerName(ReadOnlyCollection<ClaimSet> claimSets)
    {
        string owner = string.Empty;
        foreach (ClaimSet claims in claimSets)
        {
            foreach (Claim claim in claims)
            {
                if (claim.ClaimType.Equals(ClaimTypes.Name) && 
                     claim.Right.Equals(Rights.PossessProperty))
                {
                    owner = claim.Resource.ToString();
                    break;
                }
            }
        }
        return owner;
    }
    

    The ValidateOwner method shown provides claim sets as part of the OperationValidationEventArgs argument. Using these claim sets, it completes the message validation. Note that at this point, the actual message body parameters are not yet available. The ExtractCallerName method extracts the caller name from the Name claim and stores it. In subsequent requests, the caller name is checked against the Name claim of the incoming message to verify that the same person who sent the first message (and caused the instance creation) is the same person who sent the subsequent message.

Note

This sample requires that .NET Framework version 3.5 is installed to build and run. Visual Studio 2008 is required to open the project and solution files.

To set up, build, and run the project

  1. Perform the setup instructions in One-Time Set Up Procedure for the Windows Communication Foundation Samples.

  2. Run the CreateStores.cmd script located in the One-Time Set Up Procedure for the Windows Communication Foundation Samples topic.

  3. Build the solution.

    Run the sample as a user from the Administrators group. First start the service executable, then the client executable. If you are using Windows Vista, right-click the executable file and then click Run as administrator. The sample runs through to completion.

    Try running the sample using a different user identity (use a Run As command to run as a different identity). If the identity does not belong to the Administrators group, the service returns an error.

To run the sample on separate computers

  1. Edit the configuration files for the service and client, ensuring that you change the server name in the endpoint address. Change the server name from localhost to the machine name on which you are running the service.

  2. The service uses port 8888, therefore you must open this port in the firewall. From the Windows Control Panel, click Windows Firewall. Click Add Port and then add port 8888. Alternatively, you can run the service on the default port. To do so, remove :8888 from the endpoint address.

  3. Edit the client configuration file and add the following element as a child of the <endpoint> element.

    <identity>
        <UserPrincipalName value=”*@<Domain Name in which your server is running” />
    </identity>
    

    To determine the domain name, start the service on the computer on which you intend to run it. In a command prompt window on the other computer, run the following command.

    svcutil.exe http://<serverName>:8888/servicehost/Calculator.svc

    This command generates a .cs file and an output.config file. In the <endpoint> element of the configuration file, copy the <identity> element to your client configuration file.

© 2007 Microsoft Corporation. All rights reserved.