SharePoint Security and .NET Impersonation (transshipment)
Posted on 2005-03-23 21:50 尼斯湖李 阅读(804) 评论(0) 编辑 收藏 举报Introduction
Microsoft Windows SharePoint Services and SharePoint Portal Server 2003 have become very popular in recent years and have helped many businesses create an infrastructure for sharing and collaborating internal information. Out of the box, SharePoint offers an abundance of functionality that spans many vertical markets and divisions within an organization. From a development perspective, the SharePoint Services platform provides an IT organization the unique opportunity to focus almost exclusively on providing specific domain logic and value-added functionality to the businesses they support. During the process of extending SharePoint it is imperative to understand the SharePoint security model. Several techniques can be applied that will provide the optimum combination of ease of development, deployment, functionality and security. This article explains the fundamentals of SharePoint security under IIS's Windows Authentication mode (SharePoint's Domain Account mode). Custom Web parts will be used to demonstrate SharePoint's security infrastructure, and techniques will be taught for ensuring that an implementation is as secure as possible while providing a clean and consistent user experience. SharePoint Authentication To authenticate its users, SharePoint has the ability to run under any of the modes that are available in IIS. These include Anonymous, Basic, Integrated Windows or Certificates Authentication (over SSL). This article assumes that SharePoint will be running under Integrated Windows Authentication mode, which is common for internal scenarios where SharePoint is configured for the domain user account mode. Since SharePoint is built upon ASP.NET, it is configured in much the same way as a normal ASP.NET application. The following XML element which defines the authentication mode for a SharePoint installation can be found in the web.config file in the root of a SharePoint installation (c:\inetpub\wwwroot on my server): <authentication mode="Windows" />The <authentication> configuration element tells ASP.NET which mechanism should be used to authenticate users to the site. It is important to note that in addition to this piece of ASP.NET configuration, the SharePoint IIS Web application (Default Web Site on my server) is also configured to authenticate via Windows. The available authentication modes in ASP.NET are Windows, Forms, Passport or None and should not be confused with the authentication modes of IIS.
Figure 1: IIS 6.0 Authentication Methods dialog box for the SharePoint web application The <authentication> configuration element tells ASP.NET which mechanism should be used to authenticate users to the site. It is important to note that in addition to this piece of ASP.NET configuration, the SharePoint IIS Web application (Default Web Site on my server) is also configured to authenticate via Windows. The available authentication modes in ASP.NET are Windows, Forms, Passport or None and should not be confused with the authentication modes of IIS.Authorization Perhaps the most obvious use of security in SharePoint is to regulate the actions and access rights of individual users and groups of users. Integrated Windows Authentication provides a high level of security while maintaining an authentication and authorization structure that lives in harmony with a typical Windows domain. Individual domain users and groups can be given access rights to SharePoint sites and resources by being added to SharePoint site groups. A SharePoint site group defines a role such as "Readers", which allows read-only access to a given site. In addition to Readers, there are several more site groups built into SharePoint including Contributors, Web Designers and Administrators. By adding domain users and groups to SharePoint site groups, the authorization structure of SharePoint security begins to take shape. It is also possible to create custom site groups to encapsulate different combinations of permissions. As an example of typical authorization structure, consider the following: I create one domain group for my Accounting department and assign it to both the Readers and Contributors site groups on the SharePoint Accounting Web site. I do the same thing for my HR department and its SharePoint site, but I only add my HR domain group to the Readers site group of the Accounting Web site. This allows me to implicitly control membership to SharePoint content based on membership in divisional domain groups. HR has read-only access to content on the Accounting site and Account has read-only access to the HR site. Security can get much more complex than this, but this demonstrates the capabilities of SharePoint authorization as it relates to the Windows domain. Impersonation To make Windows security integration possible, SharePoint utilizes .NET impersonation. .NET Impersonation allows an application to run under the context of the client accessing an application. With ASP.NET impersonation, IIS is responsible for authenticating users against the domain and passing to ASP.NET an authenticated token, which can then act on behalf of the client. ASP.NET impersonation can be defined implicitly through configuration settings, or, as covered later in this article, explicitly through code. A close look at the web.config file for a SharePoint site reveals the implicit impersonation configuration for a SharePoint installation running under Integrated Windows Authentication mode: <identity impersonate="true" />This setting instructs ASP.NET to implicitly act on the behalf of the client who is accessing SharePoint. This setting instructs ASP.NET to implicitly act on the behalf of the client who is accessing SharePoint. The Problem The time will come during a SharePoint customization project where a developer will want .NET code to perform some action that all users of the SharePoint site do not have the permission to perform. For example, accessing and displaying data from lists on other SharePoint sites and virtual servers (through the SharePoint object model), accessing Active Directory, or pushing files to a non-public shared drive. A developer may even need to access data from a SQL Server instance that requires Windows Authentication. Some users will possess the necessary permissions to perform these operations while others won't, and we certainly do not want to grant these permissions to everyone. In all cases where the client does not possess the proper permissions to perform any of the actions listed above, SharePoint will cause Internet Explorer to prompt the user for login credentials. To avoid this usability interruption and facilitate smooth access to privileged operations and resources, a custom impersonation solution must be implemented. The code performing these actions must be run under the context of an account that has permission to perform the necessary tasks. After the operations are performed, the security principal should revert back to the client's domain account which SharePoint was originally impersonating.
Figure 2 - Internet Explorer's network login dialog box The Impersonator Class There are several steps involved with performing explicit impersonation, which I have wrapped up into a class called Impersonator (download here). The steps for impersonation are as follows:
The goal of wrapping these steps into one class is to write the following simple code to begin impersonation, execute code under the context of a different domain user account, and revert back to the original security context of the client: Impersonator i = new Impersonator("SharePointRead", "MARINER", "password123").Impersonate(); // Code requiring higher permissions... i.Undo();Note: Instead of hard-coding the account credentials, they should probably be stored in the SharePoint's web.config file or a SQL Server table. The first step is to authenticate the user account that has permission to perform the necessary operation. One way of doing this is by utilizing the Win32 LogonUser API call. To make a Win32 call from ASP.NET, we can make use of P/Inoke with the following method declaration: [DllImport("advapi32.dll", SetLastError=true)] private static extern bool LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, int dwLogonType, int dwLogonProvider, ref IntPtr phToken);The LogonUser API function attempts to authenticate an account to a Windows domain with the security credentials that are passed to it. Notice that in the DllImport attribute declaration we are specifying the name of the library in which the function exists and are setting the SetLastError property value equal to true. If the logon is successful LogonUser returns true and also passes a token handle back to the caller that represents the authenticated user to impersonate. This handle is stored in an IntPtr object when returned to a .NET application. If the operation fails, LogonUser returns false and the error code can be retrieved with the following line of code: int errorCode = Marshal.GetLastWin32Error();Marshal.GetLastWin32Error() is a static method that retrieves the last error thrown by an unmanaged P/Invoke call whose SetLastError value is set to true in the DllImport statement. Now that we have a token representing our authenticated user, an instance of WindowsIdentity can be created. WindowsIdentity implements the System.Security.Principal.IIdentity interface and is used to represent a Windows user in a .NET application. A WindowsIdentity instance representing the impersonation principal that is needed can be created by simply passing the token handle that was received from LogonUser to the WindowsIdentity constructor: WindowsIdentity winIdentity = new WindowsIdentity(handle);The Impersonator class has a public method called Impersonate() which wraps a call to the WindowsIdentity.Impersonate() method. WindowsIdentity.Impersonate() returns a System.Security.Principal.WindowsImpersonationContext instance, which is stored as a private member of the Impersonator class. The WindowsImpersonationContext instance represents a Windows user before impersonation begins and allows us to stop impersonation by calling its Undo() method. The Impersonator.Undo() method wraps the call to WindowsImpersonationContext.Undo(). Consider the following code extract from the Impersonator class: private WindowsImpersonationContext impersonationContext = null; public void Impersonate() { //Logon() returns the WindowsImpersonationContext instance needed this.impersonationContext = this.Logon().Impersonate(); } public void Undo() { this.impersonationContext.Undo(); }Please refer to the accompanying C# files for the complete Impersonator class source code and an example of its use. Note: Instead of hard-coding the account credentials, they should probably be stored in the SharePoint's web.config file or a SQL Server table. The LogonUser API function attempts to authenticate an account to a Windows domain with the security credentials that are passed to it. Notice that in the DllImport attribute declaration we are specifying the name of the library in which the function exists and are setting the SetLastError property value equal to true. If the logon is successful LogonUser returns true and also passes a token handle back to the caller that represents the authenticated user to impersonate. This handle is stored in an IntPtr object when returned to a .NET application. If the operation fails, LogonUser returns false and the error code can be retrieved with the following line of code: Marshal.GetLastWin32Error() is a static method that retrieves the last error thrown by an unmanaged P/Invoke call whose SetLastError value is set to true in the DllImport statement. The Impersonator class has a public method called Impersonate() which wraps a call to the WindowsIdentity.Impersonate() method. WindowsIdentity.Impersonate() returns a System.Security.Principal.WindowsImpersonationContext instance, which is stored as a private member of the Impersonator class. The WindowsImpersonationContext instance represents a Windows user before impersonation begins and allows us to stop impersonation by calling its Undo() method. The Impersonator.Undo() method wraps the call to WindowsImpersonationContext.Undo(). Consider the following code extract from the Impersonator class: Please refer to the accompanying C# files for the complete Impersonator class and an example of its use. Deployment and Code Access Security .NET Code Access Security (CAS) provides a security model that restricts the operations that can be performed and resources that can be accessed by managed code. The security rules imposed by CAS are applied based on a combination of facts about the origin of code and its runtime environment, not the account under which the code is executing. The full extent of CAS is well beyond the scope of this article, but do note that in order for managed code to make unmanaged calls though P/Invoke and manipulate the principal security object within ASP.NET, as the Impersonator class does, SharePoint's security policy must be altered a bit. For the sake of this article, let's assume that we need to deploy a Web part that utilizes the Impersonator class. Our code is compiled into an assembly named WebParts.dll, and it will be deployed to the bin directory of the root SharePoint installation (c:\inetpub\wwwroot\bin on my server). After deploying the Web part via stsadm.exe several changes need to be made to the file that defines the security context for Windows SharePoint Services. The default policy file, wss_minimaltrust.config, for WSS can be found in C:\Program Files\Common Files\Microsoft Shared\web server extensions\60\CONFIG. You will want to make a copy of this file and call it wss_customtrust.config. To enable the use of unmanaged code and to exercise control over the ASP.NET security principal, the following changes will have to be made to the SharePoint security policy:
The security permission classes required for our Impersonator code to run under ASP.NET are EnvironmentPermission and SecurityPermission. SecurityPermission is referenced in the policy file by default, so the only reference we need to add is for the EnvironmentPermission. The following XML fragment should be added somewhere within the <SecurityClasses> element of wss_customtrust.config: <SecurityClass Name="EnvironmentPermission" Description="System.Security.Permissions.EnvironmentPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"/>Next, a new permission set must be added to the custom policy file. This permission set defines a group of permissions that will eventually allow our Web part's code to perform COM interoperability and manipulate the application's security principal object. For these two security privileges, we add the UnmanagedCode and ControlPrincipal flags to the SecurityPermission definition and set the Unrestricted property of the EnvironmentPermission class equal to true: <PermissionSet class="NamedPermissionSet" version="1" Name="WebPartCustomTrust"> <IPermission class="SecurityPermission" version="1" Flags="Execution,UnmanagedCode,ControlPrincipal" /> <IPermission class="EnvironmentPermission" version="1" Unrestricted="true" /> </PermissionSet>Place this PermissionSet definition as a child under the <NamedPermissionSets> element in the wss_customtrust.config policy file. Now we can create a code group called WebPartCustomTrust to which the permission set above will be applied. To determine which assemblies will belong this code group, the UrlMembershipCondition is used. In our case membership will be granted exclusively to the Mariner.WebParts.dll assembly (look at the Url attribute). The following snippet should be added to the policy file as a child of the first <CodeGroup> element (code groups are hierarchical). <CodeGroup class="UnionCodeGroup" version="1" PermissionSetName="WebPartCustomTrust"> <IMembershipCondition class="UrlMembershipCondition" version="1" Url="$AppDirUrl$/bin/Mariner.WebParts.dll" /> </CodeGroup>Save the changes to wss_customtrust.config. Finally, two small changes need to be made to WSS's web.config file to point it at the new security policy. Under the <SecurityPolicy> element, a new <trustLevel> element must be configured to take advantage of the policy file that we created. The new <trustLevel> element creates a reference to wss_customtrust.config and will looks like this: <trustLevel name="WSS_Custom" policyFile="C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\60\config\wss_customtrust.config" /> In the <trust> element of the web.config file, change the level attribute to "WSS_Custom", thereby pointing it at the policy reference we just created. Save the web.config file. <trust level="WSS_Custom" originUrl="" />At this point all of the necessary configuration pieces are in place to impersonate a high-privileged user in a very secure way in SharePoint. Running iisreset from the command line will ensure that the new policy is applied to the SharePoint application. Next, a new permission set must be added to the custom policy file. This permission set defines a group of permissions that will eventually allow our Web part's code to perform COM interoperability and manipulate the application's security principal object. For these two security privileges, we add the UnmanagedCode and ControlPrincipal flags to the SecurityPermission definition and set the Unrestricted property of the EnvironmentPermission class equal to true: Place this PermissionSet definition as a child under the <NamedPermissionSets> element in the wss_customtrust.config policy file. Save the changes to wss_customtrust.config. At this point all of the necessary configuration pieces are in place to impersonate a high-privileged user in a very secure way in SharePoint. Running iisreset from the command line will ensure that the new policy is applied to the SharePoint application. Conclusion SharePoint and ASP.NET take advantage of .NET Impersonation to manage resource access and operations privileges. When implementing custom components that require access to restricted resources, implicit impersonation must be used. By wrapping all of the steps in to the easy-to-use Impersonator class, impersonation can be accomplished in a tidy manner within code. The final step is to properly configure the SharePoint application for secure access to the extended functionality that can be found in ASP.NET. When you develop components for SharePoint remember that it is simply a robust ASP.NET application. SharePoint's is easily extensible as long as an understanding of its architecture exists. References LogonUser (advapi32.dll):
CloseHandle (kernel32.dll):
ASP.NET Impersonation:
Managing Users and Cross-site Groups:
SecurityPermissionFlag Enumeration:
EnvironmentPermission Class
Packaging and Deploying Web Parts for Microsoft Windows SharePoint Services
About the Author Jay Nathan is a Senior Consultant at Mariner, a business intelligence consulting firm headquartered in Charlotte, NC. He is a Microsoft Certified Professional and has been a Consultant, Solutions Architect and Project Team Lead delivering solutions to the power generation, telecommunications and healthcare industries. Jay's areas of expertise lie in the Microsoft .NET technologies, SQL Server, and BizTalk Server and are augmented by experience in database design, ETL and data conversion, collaborative, and reporting solutions development. Jay is currently working with Microsoft tools such as SharePoint Portal Services, Reporting Services, Analysis Services and Microsoft CRM. He can be reached at jnathan@mariner-usa.com or visit his blog. |