How To Use SharePoint 2010 Secure Store As Single Sign-On Service For SAP Applications Using ERPConnect

*** NEW! My blog moved to my homepage at http://www.parago.de! ***

The Secure Store Service in SharePoint 2010 replaces the Single Sign-on Shared Service of MOSS 2007 and provides an easy way to map user credentials of external resources like SAP systems to Windows users. During the process of developing SAP interfaces using the very handy ERPConnect library from Theobald Software you have to open a R3 connection with the SAP system using SAP account credentials (username and password).

In most cases you will use a so called technical user with limited access rights to execute or query objects in SAP, but a SAP system saves a lot of sensitive data which cannot all be shown to all users. So, creating a new secure store in SharePoint 2010 to save the SAP user credentials will be the solution. Accessing the secure store from program code is quite simple.

A trail version of the ERPConnect library can be downloaded at www.theobald-software.com.

Secure Store Configuration

The Secure Store Service will be managed by the Central Administration (CA) of SharePoint 2010 under Application Management > Manage service applications > Secure Store Service:

Screenshot1

As the screenshot above shows is it possible to create multiple target applications within one Secure Store Service.

Clicking the New button will open the Create New Secure Store Target Application page. In this dialog you have to enter the new Target Application ID, a display name, a contact email address and other application related details (see screenshot below).

Screenshot2

Next, the application fields must be defined:

Screenshot3

It’s important to select the field type User Name and Password, because our implementation later on will check the target application for those two field types.

In the last dialog step the application administrator must be defined. After defining the administrator and clicking the Ok button SharePoint is creating a new secure store:

Screenshot5

Next, the Windows users must be mapped to the SAP user credentails. Therefore mark the checkbox for the newly created secure store SAPCredentialsStore and click the Set button in the toolbar. This opens the following dialog:

Screenshot6

The Credential Owner is the Windows user for whom the SAP user credentials will be set. Enter the SAP username and password and click the Ok button to save them.

That’s it !

Secure Store Programming

Accessing the secure store by code is simple. We implement a SecureStore class which will encapsulate all the access logic. A second class called SecureStoreCredentials contains the retrieved user credentials returned by the method GetCurrentCredentials of the SecureStore class.

But first, we need to create a Visual Studio 2010 SharePoint project and reference a couple of assemblies. You can directly enter the file paths in the Reference dialog (Browse tab) to add the assemblies:

Microsoft.BusinessData.dll
C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\14\ISAPI\Microsoft.BusinessData.dll

Microsoft.Office.SecureStoreService.dll
C:\Windows\assembly\GAC_MSIL\Microsoft.Office.SecureStoreService\14.0.0.0__71e9bce111e9429c\Microsoft.Office.SecureStoreService.dll

The following code shows the SecureStore class implementation:

internal class SecureStore
{
  public string ApplicationId { get; private set; }

  public SecureStore(string applicationId)
  {
    if(string.IsNullOrEmpty(applicationId))
      throw new ArgumentNullException("applicationId");
    if(!IsApplicationValid(applicationId))
      throw new ArgumentException(string.Format("Target application with ID '{0}' is not defined.", applicationId));

    ApplicationId = applicationId;
  }

  public SecureStoreCredentials GetCurrentCredentials()
  {
    SecureStoreProvider provider = new SecureStoreProvider { Context = SPServiceContext.Current };
    string userName = string.Empty;
    string password = string.Empty;

    using(SecureStoreCredentialCollection data = provider.GetCredentials(ApplicationId))
    {
      foreach(ISecureStoreCredential c in data)
      {
        if(c != null)
        {
          if(c.CredentialType == SecureStoreCredentialType.UserName)
            userName = GetDecryptedCredentialString(c.Credential);
          else if(c.CredentialType == SecureStoreCredentialType.Password)
            password = GetDecryptedCredentialString(c.Credential);
        }
      }
    }

    if(string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password))
      throw new SecureStoreException("Credentials for the current Windows user are not valid or not defined.");

    return new SecureStoreCredentials(userName, password);
  }

  public static bool IsApplicationValid(string applicationId)
  {
    if(string.IsNullOrEmpty(applicationId))
      throw new ArgumentNullException("applicationId");

    SecureStoreProvider provider = new SecureStoreProvider { Context = SPServiceContext.Current };

    foreach(TargetApplication application in provider.GetTargetApplications())
    {
      if(application.ApplicationId == applicationId)
      {
        ReadOnlyCollection<ITargetApplicationField> fields = provider.GetTargetApplicationFields(applicationId);
        bool existsUserNameDefinition = false;
        bool existsPasswordDefinition = false;

        foreach(TargetApplicationField field in fields)
        {
          if(field.CredentialType == SecureStoreCredentialType.UserName)
            existsUserNameDefinition = true;
          else if(field.CredentialType == SecureStoreCredentialType.Password)
            existsPasswordDefinition = true;
        }

        if(existsUserNameDefinition && existsPasswordDefinition)
          return true;
      }
    }

    return false;
  }

  public static string GetDecryptedCredentialString(SecureString secureString)
  {
    IntPtr p = Marshal.SecureStringToBSTR(secureString);

    try
    {
      return Marshal.PtrToStringUni(p);
    }
    finally
    {
      if(p != IntPtr.Zero)
        Marshal.FreeBSTR(p);
    }
  }
}

The constructor checks if an application ID is passed and if it’s valid by calling the static method IsApplicationValid. In first place, the IsApplicationValid method is creating an instance of the SecureStoreProvider class to get access to Secure Store Service. The SecureStoreProvider class provides all methods to talk to the SharePoint service. Then, the method queries for all target applications and checks for the given application. If the application has been created and can be found, the method will analyse the application field definitions. The IsApplicationValid method then looks for two fields of type User Name and Password (see above).

The GetCurrentCredentials method is actually trying to get the SAP user credentials from the store. The method is creating an instance of the SecureStoreProvider class to get access to service and then calls the GetCredentials method of the provider class. If credentials are available the method decrypt the username and password from type SecureString using the internal method GetDecryptedCredentialString. It then will wrap and return the data into an instance of the SecureStoreCredentails class.

For more details of the implementation see the source code.

Accessing SAP Using The Secure Store Credentials

The sample and test code calls the SAP function module SD_RFC_CUSTOMER_GET to retrieve all customer data that match certain criteria (where NAME1 starts with Te*):

Screenshot7

The following code shows the implementation of the Test button click event:

protected void OnTestButtonClick(object sender, EventArgs e)
{
  string licenseKey = "<LICENSEKEY>";
  string connectionStringFormat = "CLIENT=800 LANG=EN USER={0} PASSWD={1} ASHOST=HAMLET ...";

  R3Connection connection = null;

  try
  {
    LIC.SetLic(licenseKey);

    ...

    SecureStoreCredentials credentials =
      new SecureStore(ApplicationID.Text).GetCurrentCredentials();
    string connectionstring =
      string.Format(connectionStringFormat, credentials.UserName, credentials.Password);

    connection = new R3Connection(connectionstring);
    connection.Open();

    RFCFunction function = connection.CreateFunction("SD_RFC_CUSTOMER_GET");
    function.Exports["NAME1"].ParamValue = "Te*";
    function.Execute();

    ResultGrid.DataSource = function.Tables["CUSTOMER_T"].ToADOTable();
    ResultGrid.DataBind();

    OutputLabel.Text = string.Format("The test called...",
      ApplicationID.Text, Web.CurrentUser.Name, Web.CurrentUser.LoginName,
      credentials.UserName);
  }
  catch(Exception ex)
  {
    WriteErrorMessage(ex.Message);
  }
  finally
  {
    if(connection != null && connection.Ping())
      connection.Close();
  }
}

The interesting part is the retrieval of the SAP user credentials from the secure store defined in the text box named ApplicationID. The application ID will be passed as parameter to the constructor of the SecureStore class. After creating the instance the method GetCurrentCredentials will be called to ask the store for the credentials of the current Windows user.

After the user credential query has been successfully executed the SAP connection string will be constructed. Then the connection string will then be used to create an instance of the R3Connection class to connect with the SAP system. The remaining code is just calling the function module SD_RFC_CUSTOMER_GET and binding the result to the SPGridView.

Download Source Code | Download Article (PDF)

Advertisements

How To Implement A Modern Progress Dialog For WPF Applications

*** NEW! My blog moved to my homepage at http://www.parago.de! ***

Developing a Windows Presentation Foundation (WPF) application requires sometimes to execute an asynchronous task with long-running execution steps, e.g. calling a web service. If those steps are triggered by user interactions, you want show a progress dialog to block the user interface and display detail step information. In some cases you also want to allow the user to stop the long-running task by clicking a Cancel button.

The following screenshots show some samples of progress dialogs implemented in this blog entry:

image

image

image

image

This article will show how to implement such progress dialogs in WPF with C#. All of the above dialogs can be used with one implementation. The solution will also show how to hide the close button of a window, which is officially not supported by WPF.

The code snippet below shows how to use and display such a progress dialog:

ProgressDialogResult result = ProgressDialog.Execute(this, "Loading data...", () => {

  // TODO: Do your work here!

});

if(result.OperationFailed)
  MessageBox.Show("ProgressDialog failed.");
else
  MessageBox.Show("ProgressDialog successfully executed.");

The ProcessDialog class provides a number of static Execute methods (overrides) to easily setup a long-running task with a dialog window. The first parameter always defines the parent window in order to center the process dialog relative to the parent window. The second parameter is the text message displayed in all dialogs. The third parameter is the asynchronous method itself.

The fourth parameter allows to pass additional settings. Those dialog settings, represented by an instance of the ProcessDialogSettings class, define if the dialog shows a sub label, has a Cancel button or displays the progress bar itself in percentage or indeterminate. Predefined settings are also defined as static properties.

Using lambda expressions in C#, it is very comfortable to start a long-running task and displaying the dialog. You can also communicate with the progress dialog to report messages to the user by calling the Report method of the ProgressDialog class:

ProgressDialogResult result = ProgressDialog.Execute(this, "Loading data...", (bw) => {

  ProgressDialog.Report(bw, "Connecting to the server...");

  // TODO: Connect to the server

  ProgressDialog.Report(bw, "Reading metadata...");

  // TODO: Reading metadata

}, ProgressDialogSettings.WithSubLabel);

The two samples above did not show a Cancel button. The next code shows a progress dialog with a Cancel button including code to check if the long-running code must be cancelled:

int millisecondsTimeout = 1500;

ProgressDialogResult result = ProgressDialog.Execute(this, "Loading data", (bw, we) => {

  for(int i = 1; i <= 5; i++)
  {
    if(ProgressDialog.ReportWithCancellationCheck(bw, we, "Executing step {0}/5...", i))
      return;

    Thread.Sleep(millisecondsTimeout);
  }

  // So this check in order to avoid default processing after the Cancel button has been
  // pressed. This call will set the Cancelled flag on the result structure.
  ProgressDialog.CheckForPendingCancellation(bw, we);

}, ProgressDialogSettings.WithSubLabelAndCancel);

if(result.Cancelled)
  MessageBox.Show("ProgressDialog cancelled.");
else if(result.OperationFailed)
  MessageBox.Show("ProgressDialog failed.");
else
  MessageBox.Show("ProgressDialog successfully executed.");

Calling the ReportWithCancellationCheck method will check for a pending cancellation request (from the UI) and will may set the Cancel property of the DoWorkEventArgs class passed from the underlying BackgroundWorker object to True. Otherwise the method will display the message and continue processing.

The Exceute method of the ProgressDialog class will return an instance of the ProgressDialogResult class that returns the status of the execution. Thrown exceptions in the task method will be stored in the Error property if the OperationFailed property is set to True. For more samples see the Visual Studio 2010 solution you can download from my homepage.

The ProgressDialog window contains the main application logic. The above described static Execute methods will internally call the ExecuteInternal method to create an instance of the ProgressDialog window passing and setting all necessary values. Then the Execute method with the asynchronous method as parameter is called:

internal static ProgressDialogResult ExecuteInternal(Window owner, string label,
  object operation, ProgressDialogSettings settings)
{
  ProgressDialog dialog = new ProgressDialog(settings);
  dialog.Owner = owner;

  if(!string.IsNullOrEmpty(label))
    dialog.Label = label;

  return dialog.Execute(operation);
}

The operation method can be a delegate of the following type:

· Action

· Action<BackgroundWorker>

· Action<BackgroundWorker, DoWorkEventArgs>

· Func<object>

· Func<BackgroundWorker, object>

· Func<BackgroundWorker, DoWorkEventArgs, object>

The Func types can return a value that will be used to set the Result property of the ProgressDialogResult class.

The Excute method is implemented as follows:

internal ProgressDialogResult Execute(object operation)
{
  if(operation == null)
    throw new ArgumentNullException("operation");

  ProgressDialogResult result = null;

  _isBusy = true;

  _worker = new BackgroundWorker();
  _worker.WorkerReportsProgress = true;
  _worker.WorkerSupportsCancellation = true;

  _worker.DoWork +=
    (s, e) => {
      if(operation is Action)
        ((Action)operation)();
      else if(operation is Action<BackgroundWorker>)
        ((Action<BackgroundWorker>)operation)(s as BackgroundWorker);
      else if(operation is Action<BackgroundWorker, DoWorkEventArgs>)
        ((Action<BackgroundWorker, DoWorkEventArgs>)operation)(s as BackgroundWorker, e);
      else if(operation is Func<object>)
        e.Result = ((Func<object>)operation)();
      else if(operation is Func<BackgroundWorker, object>)
        e.Result = ((Func<BackgroundWorker, object>)operation)(s as BackgroundWorker);
      else if(operation is Func<BackgroundWorker, DoWorkEventArgs, object>)
        e.Result = ((Func<BackgroundWorker, DoWorkEventArgs, object>)operation)(s as BackgroundWorker, e);
      else
        throw new InvalidOperationException("Operation type is not supoorted");
    };

  _worker.RunWorkerCompleted +=
    (s, e) => {
      result = new ProgressDialogResult(e);
      Dispatcher.BeginInvoke(DispatcherPriority.Send, (SendOrPostCallback)delegate {
        _isBusy = false;
        Close();
      }, null);
    };

  _worker.ProgressChanged +=
    (s, e) => {
      if(!_worker.CancellationPending)
      {
        SubLabel = (e.UserState as string) ?? string.Empty;
        ProgressBar.Value = e.ProgressPercentage;
      }
    };

  _worker.RunWorkerAsync();

  ShowDialog();

  return result;
}

The ProgressDialog class is using internally a BackgroundWorker object to handle the asynchronous execution of the task or operation method.

For more details of the implementation see the source code.

Download Source Code

Download Article (PDF)

UPDATE:

I wrote a second edition of the progress dialog which is using a ProgressDialogContext class to access BackgroundWorker and the DoWorkEventArgs. Also, all Report methods moved to the context class. So, you can basically use the static ProgressDialog.Current property within the asynchronous method and its sub methods to report to the progress dialog. This usefully if you call a chain of methods in which you have to report messages to the dialog and may need to cancel the process.

Download Source Code with ProgresDialogContext class