Second Life of a Hungarian SharePoint Geek

March 13, 2012

Implementing a simple secure store provider

Filed under: BCS, Security, SP 2010 — Tags: , , — Peter Holpar @ 23:27

In my recent post I described two alternatives for accessing external data from the OWS process. One of the options – the RevertToSelf authentication – is not the best choice for a production environment. The other options – the Impersonate Windows Identity authentication mode – requires Secure Store Service (SSS) service, that is a SharePoint Server 2010 feature. It means you might have no ideal solution if you have a SharePoint Foundation 2010 environment.

Although the out-of-the-box Secure Store Implementation is the Microsoft.Office.SecureStoreService.Server.SecureStoreProvider class (Microsoft.Office.SecureStoreService assembly), that is really SharePoint Server only, the main interfaces located in the Microsoft.BusinessData.Infrastructure.SecureStore namespace(Microsoft.BusinessData assembly), that is available in SharePoint Foundation as well.

It means we can implement a custom Secure Store Implementation for SharePoint Foundation.

Below I show you a very basic sample for that. Of course, you can create more sophisticated versions based on the same concepts.

In Visual Studio 2010 we should create a new Class Library project. The target framework should be set to .NET 3.5 and platform target as x64. Since we have to deploy our assembly to GAC, we need a key file as well, to sign the strong-named assembly.

First I include the code for the helper classes.

The SimpleTargetApplicationDefinition class holds information about our target application.

  1. namespace SimpleSecureStoreProvider
  2. {
  3.     public class SimpleTargetApplicationDefinition : ITargetApplicationDefinition
  4.     {
  5.         public string ContactEmail { get; internal set; }
  6.  
  7.         public Uri CredentialManagementUrl { get; internal set; }
  8.  
  9.         public string FriendlyName { get; internal set; }
  10.  
  11.         public string Name { get; internal set; }
  12.  
  13.         public TargetApplicationType Type { get; internal set; }
  14.     }
  15. }

The SimpleTargetApplicationField describes a field of the target application.

  1. namespace SimpleSecureStoreProvider
  2. {
  3.     public class SimpleTargetApplicationField : ITargetApplicationField
  4.     {
  5.         public SecureStoreCredentialType CredentialType { get; internal set; }
  6.  
  7.         public bool IsMasked { get; internal set; }
  8.  
  9.         public string Name { get; internal set; }
  10.     }
  11. }

The SimpleSecureStoreCredential class contains a piece of the credential information.

  1. namespace SimpleSecureStoreProvider
  2. {
  3.     public class SimpleSecureStoreCredential : ISecureStoreCredential
  4.     {
  5.         bool disposed;
  6.  
  7.         public SecureString Credential { get; internal set; }
  8.  
  9.         public SecureStoreCredentialType CredentialType { get; internal set; }
  10.  
  11.         public void Dispose()
  12.         {
  13.             this.Dispose(true);
  14.         }
  15.  
  16.         private void Dispose(bool disposing)
  17.         {
  18.             if (!disposed)
  19.             {
  20.                 if (Credential != null)
  21.                 {
  22.                     Credential.Dispose();
  23.                 }
  24.                 disposed = true;
  25.             }
  26.         }
  27.  
  28.     }
  29. }

The main part of the code is the provider itself, that is SimpleSecureStoreProvider class. The skeleton of the class is like this:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Collections.ObjectModel;
  6. using Microsoft.BusinessData.Infrastructure.SecureStore;
  7. using System.Diagnostics;
  8. using System.Security;
  9.  
  10. namespace SimpleSecureStoreProvider
  11. {
  12.     public class SimpleSecureStoreProvider : ISecureStoreProviderExtended, ISecureStoreProvider
  13.     {
  14.     
  15.         …
  16.  
  17.     }
  18. }

We should return the application definition in the GetTargetApplication method. In this sample we return always the same data, regardless of the appId parameter.  As you can see, the sample is an example for a Group Target Application Type. In the GetTargetApplications method we return a single target application. I’ve included Trace commands to help us to monitor which methods are invoked in runtime.

  1. public ITargetApplicationDefinition GetTargetApplication(string appId)
  2. {
  3.     Trace.TraceInformation("GetTargetApplication {0} ({1})", appId, Process.GetCurrentProcess().Id);
  4.     return new SimpleTargetApplicationDefinition
  5.     {
  6.         ContactEmail = "user@company.com",
  7.         CredentialManagementUrl = null,
  8.         FriendlyName = "TargetApp",
  9.         Name = "AppDef",
  10.         Type = TargetApplicationType.Group
  11.     };
  12. }
  13.  
  14. public ReadOnlyCollection<ITargetApplicationDefinition> GetTargetApplications()
  15. {
  16.     Trace.TraceInformation("GetTargetApplications ({1})", Process.GetCurrentProcess().Id);
  17.     return new ReadOnlyCollection<ITargetApplicationDefinition>(new List<ITargetApplicationDefinition>() { GetTargetApplication("app") });
  18. }

In this application we have two fields, one for the user name and one for the password, as shown in the GetTargetApplicationFields method.

  1. public ReadOnlyCollection<ITargetApplicationField> GetTargetApplicationFields(string appId)
  2. {
  3.     Trace.TraceInformation("GetTargetApplicationFields {0} ({1})", appId, Process.GetCurrentProcess().Id);
  4.     List<ITargetApplicationField> fields = new List<ITargetApplicationField>();
  5.     fields.Add(new SimpleTargetApplicationField
  6.     {
  7.         CredentialType = SecureStoreCredentialType.WindowsUserName,
  8.         IsMasked = false,
  9.         Name = "UserName"
  10.     });
  11.     fields.Add(new SimpleTargetApplicationField
  12.     {
  13.         CredentialType = SecureStoreCredentialType.WindowsPassword,
  14.         IsMasked = false,
  15.         Name = "Password"
  16.     });
  17.  
  18.     return new ReadOnlyCollection<ITargetApplicationField>(fields);
  19. }

In the GetCredentials and GetRestrictedCredentials methods we should return credentials for the application. In this case I have a user name and password hardcoded, but these should be stored in a bit more secure and configurable way.

  1. public SecureStoreCredentialCollection GetCredentials(string appId)
  2. {
  3.     Trace.TraceInformation("GetCredentials {0} ({1})", appId, Process.GetCurrentProcess().Id);
  4.  
  5.     List<ISecureStoreCredential> creds = new List<ISecureStoreCredential>();
  6.  
  7.     creds.Add(new SimpleSecureStoreCredential
  8.     {
  9.         Credential = GetSecureString(@"domain\user"),
  10.         CredentialType = SecureStoreCredentialType.UserName
  11.     });
  12.     creds.Add(new SimpleSecureStoreCredential
  13.     {
  14.         Credential = GetSecureString("password"),
  15.         CredentialType = SecureStoreCredentialType.Password
  16.     });
  17.     return new SecureStoreCredentialCollection(creds);
  18. }
  19.  
  20. public SecureStoreCredentialCollection GetRestrictedCredentials(string appId)
  21. {
  22.     Trace.TraceInformation("GetRestrictedCredentials {0} ({1})", appId, Process.GetCurrentProcess().Id);
  23.  
  24.     return GetCredentials(appId);
  25. }

The GetSecureString method helps to convert a string into SecureString.

  1. private SecureString GetSecureString(string value)
  2. {
  3.     SecureString result = new SecureString();
  4.     if (value != null)
  5.     {
  6.         value.ToCharArray().ToList().ForEach(c => result.AppendChar(c));
  7.     }
  8.  
  9.     return result;
  10. }

The methods below are not implemented in this sample:

  1. public void DeleteCredentials(string appId)
  2. {
  3.     Trace.TraceInformation("DeleteCredentials {0} ({1})", appId, Process.GetCurrentProcess().Id);
  4.  
  5.     throw new NotImplementedException();
  6. }
  7.  
  8. public SecureStoreCredentialCollection GetCredentialsUsingTicket(string ticket, string appId)
  9. {
  10.     Trace.TraceInformation("GetCredentialsUsingTicket {0} ({1})", appId, Process.GetCurrentProcess().Id);
  11.  
  12.     throw new NotImplementedException();
  13. }
  14.  
  15. public string IssueTicket()
  16. {
  17.     Trace.TraceInformation("IssueTicket ({1})", Process.GetCurrentProcess().Id);
  18.  
  19.     throw new NotImplementedException();
  20. }
  21.  
  22. public ISecureStoreProviderInformation ProviderInformation
  23. {
  24.     get
  25.     {
  26.         Trace.TraceInformation("ProviderInformation ({1})", Process.GetCurrentProcess().Id);
  27.        
  28.         throw new NotImplementedException();
  29.     }
  30. }

After building the assembly and deploying it to the GAC, we should configure our BCS external system instance. Most important is to set the right Secure Store Implementation value (in my case it is SimpleSecureStoreProvider.SimpleSecureStoreProvider, simplesecurestoreprovider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9fc83bb193b5ea3b). The value of the Secure Store Target Application Id is irrelevant in this version.

image

If everything works as expected, we should be able to access the external data (in this case the Northwind database through an external list) using the credentials returned by our custom secure store implementation.

image

March 12, 2012

Solving the external data access security issue in the case of the OWS process

In the recent two posts I wrote about a security related problem we found when tried to access an external data source from the SharePoint 2010 Timer service process.

After presenting a workaround in the first part, last time I promised a real solution for the issue.

The key to the solution seems to be the information one can found on this MSDN page.

As it states, “The user security token is not available in every context. Without the security token, PassThrough security will not work.

As you may know, PassThrough is the standard and usually recommended authentication method to an external data source, but in this case we should switch to an alternative one.

The trivial solution would be to use the RevertToSelf authentication. Since this type of authentication is not recommended in a production environment, it is disabled by default. Before using it, you should enable it, for example with the help of PowerShell (see an example here).

After you enabled RevertToSelf, you can find the equivalent BDC Identity option in the list of the available authentication modes:

image

(To access the settings above, you should select the External Systems view at the administration of the Business Data Connectivity Service, then click the name of the external system you would like to manage, and then click the name of the external system instance.)

After you selected BDC Identity authentication mode, you can use this code to access the external system:

  1. using (SPSite site = new SPSite("http://sp2010&quot;))
  2. {
  3.     using (SPWeb web = site.OpenWeb())
  4.     {
  5.         Guid siteId = site.ID;
  6.         Guid webId = web.ID;
  7.  
  8.         SPSecurity.RunWithElevatedPrivileges(delegate()
  9.         {
  10.             using (SPSite siteImp = new SPSite(siteId))
  11.             {
  12.                 // access external list here
  13.             }
  14.         });
  15.     }
  16. }

The other, and recommended option for authentication is to use the Secure Store Service (SSS).

Note: Secure Store Service is not included in SharePoint Foundation 2010, so this option is unfortunately limited for SharePoint Server 2010 Standard and Enterprise versions.

Create a target application in SSS,

image

then set the credentials of an account with permissions for the external system.

Next, configure your external system to Impersonate Windows Identity, set the name of the Secure Store Target Application Id as created in the previous step, and set  Secure Store Implementation as Microsoft.Office.SecureStoreService.Server.SecureStoreProvider, Microsoft.Office.SecureStoreService, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c.

image

After you configured your external system instance as described above, you can use this code to access the external data from the timer process job / event receiver:

  1. using (SPServiceContextScope scope = new SPServiceContextScope(SPServiceContext.GetContext(web.Site)))
  2. {
  3.     // access external list within this block
  4. }

Important to note, that based on the MSDN article mentioned at the beginning of this post, workflows and sandboxed solutions might suffer from the same security problem, so if you have such issues accessing an external data source, the solutions described above might help you in these cases as well.

Theme: Shocking Blue Green. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.

Join 42 other followers