locked
WorkflowServiceHost and Thread.CurrentPrincipal RRS feed

  • Question

  • Hi,

    I am developing workflow services that receive SAML2 assertions. Based on the following post and article I have been able to receive the request and access the ClaimsPrincipal in the workflow:

    However, I have other requirements that I'm struggling to meet:

    1. Thread.CurrentPrincipal must return the ClaimsPrincipal within the workflow for calls to a business component library.
    2. The bulk of the long running workflow must continue beyond the Receive/SendReply activity pair.
    3. Workflow persistance must be supported.

    To meet these requirements, I wrote the following activity with the intent of placing any calls the the business library in InvokeMethod activities within it. The ClaimsPrincipal has been placed in a workflow variable and is passed in to the ClaimsPrincipal argument of the ClaimsPrincipalScopeActivity. It sort of seemed to work, however, I now fear despite the activity starting a No Persist zone, it is actually setting the principal on ThreadPool threads which may or may not be used for the scheduled child activity (or worse shared with another workflow executed by a different caller with a different SAML2 token), which will be recycled which would be REALLY BAD.

    What is the best way to associate a principal with a workflow running in WorkflowServiceHost?

    [Designer(typeof(ClaimsPrincipalScopeActivityDesigner))]
      public class ClaimsPrincipalScopeActivity : NativeActivity
      {
        /// <summary>
        /// Gets or sets the claims principal.
        /// </summary>
        /// <value>
        /// The claims principal.
        /// </value>
        [RequiredArgument]
        public InArgument<ClaimsPrincipal> ClaimsPrincipal
        {
          get;
          set;
        }
    
        /// <summary>
        /// Gets or sets the no persist handle.
        /// </summary>
        /// <value>
        /// The no persist handle.
        /// </value>
        private Variable<NoPersistHandle> NoPersistHandle
        {
          get;
          set;
        }
    
        /// <summary>
        /// Gets or sets the body.
        /// </summary>
        /// <value>
        /// The body.
        /// </value>
        [RequiredArgument]
        public Activity Body { get; set; }
    
    
    
        /// <summary>
        /// Creates and validates a description of the activity’s arguments, variables, child activities, and activity delegates.
        /// </summary>
        /// <param name="metadata">The activity’s metadata that encapsulates the activity’s arguments, variables, child activities, and activity delegates.</param>
        protected override void CacheMetadata(NativeActivityMetadata metadata)
        {
    
          NoPersistHandle = new Variable<NoPersistHandle>();
    
          var claimsPrincipal = new RuntimeArgument("ClaimsPrincipal",
                             typeof(ClaimsPrincipal),
                             ArgumentDirection.Out);
          metadata.AddArgument(claimsPrincipal);
    
          metadata.AddImplementationVariable(NoPersistHandle);
    
          metadata.AddChild(Body);
    
          base.CacheMetadata(metadata);
    
        }
    
        /// <summary>
        /// When implemented in a derived class, runs the activity’s execution logic.
        /// </summary>
        /// <param name="context">The execution context in which the activity executes.</param>
        protected override void Execute(NativeActivityContext context)
        {
          var noPersistHandle = NoPersistHandle.Get(context);
          noPersistHandle.Enter(context);
          Thread.CurrentPrincipal = this.ClaimsPrincipal.Get(context);
          context.ScheduleActivity(Body, OnCompleted);
        }
    
        /// <summary>
        /// Called when completed.
        /// </summary>
        /// <param name="context">The context.</param>
        /// <param name="completedInstance">The completed instance.</param>
        private void OnCompleted(NativeActivityContext context, ActivityInstance completedInstance)
        {
          var noPersistHandle = NoPersistHandle.Get(context);
          noPersistHandle.Exit(context);
        }
      }






    Tuesday, June 26, 2012 4:19 PM

Answers

  • However, I have other requirements that I'm struggling to meet:

    1. Thread.CurrentPrincipal must return the ClaimsPrincipal within the workflow for calls to a business component library.
    2. The bulk of the long running workflow must continue beyond the Receive/SendReply activity pair.
    3. Workflow persistance must be supported.
    I believe the canonical way of meeting requirement 1 is to use an Execution Property that implements IExecutionProperty. For further details, see the Execution Properties sample on MSDN. You could call the activity where you implement this 'ClaimsScope'.

    You can satisfy requirement 2 when using your custom activity by having SendReply be a descendent of ClaimsScope (so that the scope continues even after the reply is sent).

    Tim
    Thursday, July 5, 2012 11:29 PM

All replies

  • Since there were no replies to this, I have impemented a solution where I have moved all access the business component library mentioned in the question to CodeActivities. The code activities must derive from the following class.

    I would still be interested to know how the principal is handled in WF 4.0 and what improvements may be on the way in future?

      using System;
      using System.Activities;
      using System.Globalization;
      using System.Security.Principal;
      using System.Threading;
      using Microsoft.IdentityModel.Claims;
    
      /// <summary>
      /// Activity to attach a claims principal to the thread for use with ComponentModel 
      /// providers that require the user information on the thread. 
      /// </summary>
      public abstract class ClaimsPrincipalScopeBase : CodeActivity
      {
        /// <summary>
        /// Gets or sets the claims principal.
        /// </summary>
        /// <value>
        /// The claims principal.
        /// </value>
        [RequiredArgument]
        public InArgument<ClaimsPrincipal> ClaimsPrincipal
        {
          get;
          set;
        }
        /// <summary>
        /// When implemented in a derived class, performs the execution of the activity.
        /// </summary>
        /// <param name="context">The execution context under which the activity executes.</param>
        protected override sealed void Execute(CodeActivityContext context)
        {
          IPrincipal existingPrincipal = Thread.CurrentPrincipal;
          ClaimsPrincipal principal = this.ClaimsPrincipal.Get(context);
    
          if (principal == null)
          {
            throw new InvalidOperationException(
              string.Format(CultureInfo.InvariantCulture,
                "Principal {0} cannot be null.",
                ClaimsPrincipal));
          }
          Thread.CurrentPrincipal = principal;
    
          try
          {
            ExecuteWithClaimsPrincipalOnThread(context);
          }
          finally
          {
            Thread.CurrentPrincipal = existingPrincipal;
          }
        }
    
        /// <summary>
        /// Executes the with claims principal on thread. Deriving classes should override this method
        /// to privide an implementation.
        /// </summary>
        /// <param name="context">The context.</param>
        public abstract void ExecuteWithClaimsPrincipalOnThread(CodeActivityContext context);
      }

    Monday, July 2, 2012 10:37 AM
  • However, I have other requirements that I'm struggling to meet:

    1. Thread.CurrentPrincipal must return the ClaimsPrincipal within the workflow for calls to a business component library.
    2. The bulk of the long running workflow must continue beyond the Receive/SendReply activity pair.
    3. Workflow persistance must be supported.
    I believe the canonical way of meeting requirement 1 is to use an Execution Property that implements IExecutionProperty. For further details, see the Execution Properties sample on MSDN. You could call the activity where you implement this 'ClaimsScope'.

    You can satisfy requirement 2 when using your custom activity by having SendReply be a descendent of ClaimsScope (so that the scope continues even after the reply is sent).

    Tim
    Thursday, July 5, 2012 11:29 PM
  • Thanks Tim,

    This would look to be a solution to my ClaimsPrincipalScopeActivity.

    However, due to time constraints We'll be sticking with the ClaimsPrincipalScopeBase as code activities are adequate for calling th library.

    Monday, July 9, 2012 8:23 AM