Monday, February 27, 2012

How to use Secure Store Id for Impersonation programmatically

Sometimes you run into issue that Application Pool Account doesn’t have access to Reporting Database or to another database but you should configure credential to access Reporting Database or other databases in your application. To achieve this Microsoft SharePoint 2010 has new service - Secure Store Service. You can provide account credential and set permission for users who can manage those credentials.

This article will describe how to configure Secure Store and how to work with it from code.

First step is configuring Secure Store Id:

  1. Navigate to Central Administration -> Application Management -> Manage service applications -> Secure Store Service

  2. Click New button on the ribbon, fill out form and click Next

  3. Click Next button without any changes

  4. Provide Target Application Administrators, Members and click OK

  5. Choose just created Secure Store in the list and click Set Credentials button on the ribbon

  6. Provide account name and password


You successfully completed Secure Store Id configuration. Next step is to read account credential programmatically. The following class read credential from provided Security Store Id and returns IdentityInfo.

public class IdentityInfo
{
    public string Domain { get; set; }
    public string Username { get; set; }
    public string Password { get; set; } 
}

public class SPSecureStoreService 
{
    public IdentityInfo GetCredentials(string targetApplicationId)
    {
        var credentials = GetWindowsCredentials(targetApplicationId);
        return ParseSecureStoreCredentials(credentials);
    }

    private IdentityInfo ParseSecureStoreCredentials(Dictionary<SecureStoreCredentialType, string> credentials)
    {
        if (!credentials.ContainsKey(SecureStoreCredentialType.WindowsUserName))
            throw new ArgumentNullException("credentials", "WindowsUserName field not defined");

        if (!credentials.ContainsKey(SecureStoreCredentialType.WindowsPassword))
            throw new ArgumentNullException("credentials", "WindowsPassword field not defined");
            
        string windowsUserName = credentials[SecureStoreCredentialType.WindowsUserName];
        string windowsPassword = credentials[SecureStoreCredentialType.WindowsPassword];

        var identityInfo = new IdentityInfo();
        identityInfo.Password = windowsPassword;

        if (string.IsNullOrEmpty(windowsUserName))
            throw new ArgumentNullException("credentials", "windowsUserName not defined");
            
        // treat domain\username
        if (windowsUserName.Contains("\\"))
        {
            var parts = windowsUserName.Split('\\');

            identityInfo.Domain = parts[0];
            identityInfo.Username = parts[1];
        }
        // treat username@domain
        else if (windowsUserName.Contains("@"))
        {
            var parts = windowsUserName.Split('@');

            identityInfo.Username = parts[0];
            identityInfo.Domain = parts[1];
        }
        else
        {
            identityInfo.Username = windowsUserName;
        }

        return identityInfo;
    }

    private Dictionary<SecureStoreCredentialType, string> GetWindowsCredentials(string targetApplicationId)
    {
        var serviceContext = SPServiceContext.Current;
        var secureStoreProvider = new SecureStoreProvider { Context = serviceContext };
        var credentialMap = new Dictionary<SecureStoreCredentialType, string>();

        using (var credentials = secureStoreProvider.GetCredentials(targetApplicationId))
        {
            var fields = secureStoreProvider.GetTargetApplicationFields(targetApplicationId);
            for (var i = 0; i < fields.Count; i++)
            {
                var field = fields[i];

                var credential = credentials[i];
                var decryptedCredential = ToClrString(credential.Credential);

                credentialMap.Add(field.CredentialType, decryptedCredential);
            }
        }

        return credentialMap;
    }

    private string ToClrString(SecureString secureString)
    {
        var ptr = Marshal.SecureStringToBSTR(secureString);

        try
        {
            return Marshal.PtrToStringBSTR(ptr);
        }
        finally
        {
            Marshal.FreeBSTR(ptr);
        }
    }
}

After you get credentials you will able to impersonate to the user. The following code does impersonation:

public class Impersonator : IDisposable
{
    #region Public methods.
    // ------------------------------------------------------------------

    public Impersonator(
        string userName,
        string domainName,
        string password)
    {
        ImpersonateValidUser(userName, domainName, password);
    }

    // ------------------------------------------------------------------
    #endregion

    #region IDisposable member.
    // ------------------------------------------------------------------

    public void Dispose()
    {
        UndoImpersonation();
    }

    // ------------------------------------------------------------------
    #endregion

    #region P/Invoke.
    // ------------------------------------------------------------------

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern int LogonUser(
        string lpszUserName,
        string lpszDomain,
        string lpszPassword,
        int dwLogonType,
        int dwLogonProvider,
        ref IntPtr phToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern int DuplicateToken(
        IntPtr hToken,
        int impersonationLevel,
        ref IntPtr hNewToken);

    [DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    private static extern bool RevertToSelf();

    [DllImport("kernel32.dll", CharSet = CharSet.Auto)]
    private static extern bool CloseHandle(
        IntPtr handle);

    private const int LOGON32_LOGON_INTERACTIVE = 2;
    private const int LOGON32_PROVIDER_DEFAULT = 0;

    // ------------------------------------------------------------------
    #endregion

    #region Private member.
    // ------------------------------------------------------------------

    private void ImpersonateValidUser(
        string userName,
        string domain,
        string password)
    {
        WindowsIdentity tempWindowsIdentity = null;
        IntPtr token = IntPtr.Zero;
        IntPtr tokenDuplicate = IntPtr.Zero;

        try
        {
            if (RevertToSelf())
            {
                if (LogonUser(
                    userName,
                    domain,
                    password,
                    LOGON32_LOGON_INTERACTIVE,
                    LOGON32_PROVIDER_DEFAULT,
                    ref token) != 0)
                {
                    if (DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                    {
                        tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                        impersonationContext = tempWindowsIdentity.Impersonate();
                    }
                    else
                    {
                        throw new Win32Exception(Marshal.GetLastWin32Error());
                    }
                }
                else
                {
                    throw new Win32Exception(Marshal.GetLastWin32Error());
                }
            }
            else
            {
                throw new Win32Exception(Marshal.GetLastWin32Error());
            }
        }
        finally
        {
            if (token != IntPtr.Zero)
            {
                CloseHandle(token);
            }
            if (tokenDuplicate != IntPtr.Zero)
            {
                CloseHandle(tokenDuplicate);
            }
        }
    }

    private void UndoImpersonation()
    {
        if (impersonationContext != null)
        {
            impersonationContext.Undo();
        }
    }

    private WindowsImpersonationContext impersonationContext = null;

    // ------------------------------------------------------------------
    #endregion
}

Now you can work with your databases or other objects under user configured via Secure Store and no credentials are used directly in the code.

Enjoy!

Read more...

Wednesday, February 22, 2012

FluentPro Workspaces - New life for project sites in Microsoft Project Server 2010


Project Sites.
  • Tired of grey, static and not-visually appealing project sites?
  •  Don't like that information is in many places and I have to switch to see it?
We are tired of them too. So - we've developed a product for our customers that in its nature is a modified project site template that will change your experience with Project Sites in MS Project Server 2010.
  • Microsoft Metro-style design with home pages based on tiles that every user can customize
  • Easy access via links to native Project Web App pages - My Tasks, Timesheets, Project Schedule PDP
  • Visualized and improved risks, issues, change request and action items lists
  • Easy customization and re-use - add your own lists, pages, sub-sites (Wiki, Blog and other) and standard / customer webparts without any restrictions, save as project site template and distribute inside of your organization for different project types - this is just a native Project Site Template.



Video preview:




Visit our booth #209 at Project Conference 2012 and take a closer look on this amazing product.

Product will be launched at Project Conference 2012 and will be available for purchase.

EPM partners: Have your own products and would like to incorporate this product into your solutions? Let's talk.
Read more...

Tuesday, February 7, 2012

FluentPro Audit Lite if available for Free download


FluentPro Audit Lite is available for FREE download from our website.

Quick overview of functionality:
  • Audit changes in Enterprise Custom Fields
  • Audit changes in Enterprise Lookup Tables
  • Project creation and deletion
  • Audit changes in projects (Project level custom fields, tasks add / delete)
  • Application tracks Who, What and When
Enjoy!
Read more...