Nintex Custom Actions – Credential Picker


Continuing on from my previous post of Nintex Custom Actions – Error Handling, I thought I would also blog about using the Credential Control within a Custom Action.

The Credential Control allows the selection of predefined credential workflow constants (Central Admin > Nintex Workflow Management > Mange Workflow Constants) or a username and password to be typed in. When the action is run, the action can be run under the context those credentials. (Please Note: Username should be
domain\username, not just username as the picture indicates)

There are several Nintex actions that use this control already, a few are noted below:

  • Query User Profile
  • Web Request
  • Create AD Group
  • Add user to AD Group

Recently from working with Nintex I have used the Query User Profile


and Update User Profile actions.

Nintex doesn’t provide you with a Create User Profile, because normally with the SharePoint User Profile service, this would automatically import new users in. However, I needed the user to be in the User Profile database as soon as the user existed, I didn’t want to wait for SharePoint to complete the User Profile import. I therefore was required to make this custom action, and had the first opportunity to use the Credential Control Picker.

This blog post will show you which files you will need to update and what code is required, to make your custom action have a Credential Control Picker. I will also show you how to run the code within Nintex in a different user context. Please ensure you know how to create a Custom Action from reading my previous blog post on Creating Nintex Custom Actions, as this doesn’t show you every method and what needs to be filled in, only areas for the Credential control. What you use the Credential Picker is down to you, but you can find a working version in my Create User Profile in my previous Nintex Custom action project on my One Drive.

Looking at the project files below, these are the typical files required to create a Custom Nintex Action.

  • [name]Action.nwa – A XML file that contains Name, Category, Description, Assembly information of Activity and Adapter.
  • [name]Activity.cs – A class inheriting Nintex.Workflow.Activities.ProgressTrackingActivity which is the main execution of the custom action.
  • [name]Adapter.cs – A class inheriting Nintex.Workflow.Activities.Adapters.GenericRenderingAction, which sets up the bindings, and configuration of the custom action.
  • [name]Dialog.aspx – The aspx page to allow the user to configure the custom action.

Below is all the files required for one custom action.

Activity file.

First we will need to add public static DependencyProperties for the Username and one for the Password.

public static DependencyProperty UsernameCredentialsProperty = DependencyProperty.Register("UsernameCredentials", typeof(string), typeof(CreateUserProfileActivity));
public static DependencyProperty PasswordProperty = DependencyProperty.Register("Password", typeof(string), typeof(CreateUserProfileActivity));

Then each of these Dependency Properties will have its own public property.

//Implement Security
public string UsernameCredentials
{
  get { return (string)base.GetValue(UsernameCredentialsProperty); }
  set { base.SetValue(UsernameCredentialsProperty, value); }
}
 
public string Password
{
  get { return (string)base.GetValue(PasswordProperty); }
  set { base.SetValue(PasswordProperty, value); }
}

As you know the Activity file is where the main execution of the custom action takes place. The case is the same when using a Credential Picker, however you need to run the main bulk of your code in a different context, which will run in a different thread to the main execution.

We need to create an internal class, this class will hold all the information we need to pass between the threads. As you can see, this class will be storing the Username and Password from the control, the SiteID and the WebID of the current site the action is running in. There are two properties to pass a value in and a value out. This is a standard C# class, you can have as many or as little properties as required, minimum you need is the username and password obviously.

internal class RunDifferentThread {
    internal string username { get; set; }
    internal string password { get; set; }
    internal string valueToPassIn { get; set; }
    internal string valueToReturn { get; set; }
    internal Guid siteId { get; set; }
    internal Guid webId { get; set; }
}

Now this class has been created, I can next work on the Execute method.

protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
   string runtimeUsername = string.Empty;
   string runtimePassword = string.Empty;

   ActivityActivationReference.IsAllowed(this, __Context.Web);
   NWWorkflowContext ctx = NWWorkflowContext.GetContext(
          this.__Context,
          new Guid(this.__ListId),
          this.__ListItem.Id,
          this.WorkflowInstanceId,
          this);
 
   base.LogProgressStart(ctx);
   try
   {
       //Create RunDifferentThread Object
       CreateUserProfileActivity.RunDifferentThread runDifferentThread = new CreateUserProfileActivity.RunDifferentThread();

       //Get the runtimeUserName and runtimePassword from the Credential picker values.
       CredentialValue.DetermineRuntimeCredentials(this.UsernameCredentials, this.Password, out runtimeUsername, out runtimePassword, ctx.Web.ID, ctx.Web.Site.ID);

       //Setup values to pass to other thread.
       runDifferentThread.username = runtimeUsername;
       runDifferentThread.password = runtimePassword;

       //User Picker field within the Custom Action
       runDifferentThread.valueToPassIn = ctx.AddContextDataToString(this.AccountName);
       runDifferentThread.siteId = this.__Context.Site.ID;
       runDifferentThread.webId = this.__Context.Web.ID;
       runDifferentThread.accountToAdd = ctx.AddContextDataToString(this.AccountName);
 
       //Using System.Threading.Thread, create a new thread passing in the parameters required.
       Thread thread = new Thread(new ParameterizedThreadStart(this.Process));
       thread.Start((object)runDifferentThread);
       thread.Join();

       //A workflow variable to store the result in
       this.returnValue = runDifferentThread.valueToReturn
       base.LogProgressEnd(ctx, executionContext);
       return ActivityExecutionStatus.Closed;
   }
   catch (Exception ex)
   {
       throw;
   }
}

The code for the Execute function starts off the same as any other custom action by getting the Workflow Context. Then you need to call Nintex.Workflow.CredentialValue.DeterminRuntimeCredentials passing in the values from the UserName and Password fields. This will output the correct format values of the Username and Password. We put all the values we want to pass through to the new thread running as the new context to our runDifferentThread class. Next we create a new thread passing in the method to run Process, and the runDifferentThread. (At this point we haven’t written the Process method.) Calling thread.Join() blocks the calling thread until the other thread has terminated, which means it will wait until the method Process being called on the other thread has finished.

Then we can grab the returning value and pass it back to be stored within a workflow variable, or whatever the user has chosen to store the value in.

Below is the code for the Process method. I worked out how to do this code mainly from reflecting on how the custom action Update User Profile.

private void Process(object contextObject)
{
    //Hydrate the runDifferentThread properties
    CreateUserProfileActivity.RunDifferentThread runDifferentThread = (CreateUserProfileActivity.RunDifferentThread)contextObject;

   //This built in Nintex class allows the code to run as different user.
   Nintex.Workflow.Administration.Security security = new Nintex.Workflow.Administration.Security();

   try
   {
       //Check that the username is in the format Domain\UserName
       string[] strArray = null;

       if (runDifferentThread.username.Contains(@"\"))
       {
            strArray = runDifferentThread.username.Split(new char[] { '\\' });
       }

       if(strArray.Length != 2)
           throw new InvalidOperationException(NWResource.GetString("Exception_InvalidWindowsCredentials"));

       //Run the following code as the given user. Passing in Domain, Username and Password.        
       security.RunAsUser(strArray[1], strArray[0], runDifferentThread.password);

       //This site will run as passed in user.
       using(SPSite site = new SPSite(runDifferentThread.siteId))
       {
           //Perform you elevated permission code here

           //Return value
           runDifferentThread.valueToReturn = "My value to return from wherever"
        }
    }
    catch (Exception ex)
    {
        //Exception handling
    }
    finally
    {
       //Ensure you revert back to original user who is running the workflow.
       security.RevertToLoggedInUser();
    }
}

Walking through the Process method above, first covert the contextObject back to RunDifferentThread class. The Nintex.Workflow.Administration.Security class allows you to run as different context. After checking that the username passed in is in the correct format of Domain\Username, we then call the security.RunAsUser passing in domain, username and password. Everything after this line is ran in the user context. Please remember to finally call security.RevertToLoggedInUser(). When you update the RunDifferentThread class with new properties, these properties will be available back in the Execute method.

Adapter.cs

The adapter class has 3 main methods to change AddActivityToWorkflow, GetConfig and GetDefaultConfig. These would be the same 3 methods you would change for any controls added. At the top of the adapter class create 2 constant properties for Username and Password. These properties values should match the property names in the Activity class.

private const string UserNameCredentialsProperty = "UsernameCredentials";
private const string PasswordProperty = "Password";

Within the AddActivityToWorkflow method we need to ensure the properties are assigned to the correct Dependency property in the Activity Class.

parameters[UserNameCredentialsProperty].AssignTo(activity, CreateUserProfileActivity.UsernameCredentialsProperty, context);
parameters[PasswordProperty].AssignTo(activity, CreateUserProfileActivity.PasswordProperty, context);

The GetConfig method needs to return the values from the correct Dependency property in the Activity Class. Put this just before you return config.

parameters[UserNameCredentialsProperty].RetrieveValue(context.Activity, CreateUserProfileActivity.UsernameCredentialsProperty, context.ListId, context.Variables);
parameters[PasswordProperty].RetrieveValue(context.Activity, CreateUserProfileActivity.PasswordProperty, context.ListId, context.Variables);

The GetDefaultConfig method defines the parameters that the user can configure, therefore we need to ensure there are two parameters for Username and password.

config.Parameters[0] = new ActivityParameter();
config.Parameters[0].Name = UserNameCredentialsProperty;
config.Parameters[0].PrimitiveValue = new PrimitiveValue();
config.Parameters[0].PrimitiveValue.Value = string.Empty;
 
config.Parameters[1] = new ActivityParameter();
config.Parameters[1].Name = PasswordProperty;
config.Parameters[1].PrimitiveValue = new PrimitiveValue();
config.Parameters[1].PrimitiveValue.Value = string.Empty;

Optionally, if you want to ensure that the user fills in the User Credential field you could add the following code within the ValidateConfig method within the Adapter class.

if (!parameters[UserNameCredentialsProperty].Validate(typeof(string), context))
{
    isValid &= false;
    validationSummary.AddError(string.Empty, ValidationSummaryErrorType.RequiredCredential);
}

Dialog.aspx

Lastly the dialog.aspx page requires some code to ensure the user has an interface to enter the username/password values. Here you will need to register the Credential control, update the two JavaScript function to read in and read out the values, and lastly update the HTML to display it.

You will need to register the CredentialControl in the page.

<%@ Register TagPrefix="Nintex" TagName="CredentialControl" Src="~/_layouts/15/NintexWorkflow/CredentialControl.ascx" %>

Within the TPARetrieveConfig function you can retrieve the stored values as below. The built in Nintex JavaScript function cc_setUsername and cc_setPassword does the main work of setting the control to display previous values to the user.

if (configXml.selectSingleNode("/NWActionConfig/Parameters/Parameter[@Name='UsernameCredentials']") && configXml.selectSingleNode("/NWActionConfig/Parameters/Parameter[@Name='Password']")) {

  cc_setUsername("<%=credentialPicker.ClientID %>", configXml.selectSingleNode("/NWActionConfig/Parameters/Parameter[@Name='UsernameCredentials']/PrimitiveValue/@Value").text);

  cc_setPassword("<%=credentialPicker.ClientID %>", configXml.selectSingleNode("/NWActionConfig/Parameters/Parameter[@Name='Password']/PrimitiveValue/@Value").text);
}

The TPAWriteConfig function writes back to the configuration file. Nintex has provided a corresponding get function to read from the control.

function TPAWriteConfig() {
    EnsurePrimitiveValueNode(configXml, "UsernameCredentials");
    EnsurePrimitiveValueNode(configXml, "Password");

    configXml.selectSingleNode("/NWActionConfig/Parameters/Parameter[@Name='UsernameCredentials']/PrimitiveValue/@Value").text = cc_getUsername("<%= credentialPicker.ClientID %>");

    configXml.selectSingleNode("/NWActionConfig/Parameters/Parameter[@Name='Password']/PrimitiveValue/@Value").text = cc_getPassword("<%= credentialPicker.ClientID %>");

    return true;
}

The HTML code sits within <TemplateRowsArea>.

&lt;Nintex:ConfigurationProperty runat=&quot;server&quot; FieldTitleResourceKey=&quot;General_Credentials&quot; RequiredField=&quot;True&quot;&gt;
&lt;TemplateControlArea&gt;
        &lt;Nintex:CredentialControl DisplayMode=&quot;dialog&quot; runat=&quot;server&quot; id=&quot;credentialPicker&quot; /&gt;
&lt;/TemplateControlArea&gt;
&lt;/Nintex:ConfigurationProperty&gt;

That is all there is to it. As previously stated a working version of the Create User Profile custom action can be found in my One Drive. To use, the account that you are creating must already exist within your Active Directory. (There is already a Nintex action that creates an Active Directory user, also using the credential control picker).

Advertisements