How to configure Code Access Security for a Web Part

Posted Thursday, July 5, 2007 6:00 PM by C-Dog's .NET Tip of the Day

As a new SharePoint developer, you will be directed to install your first Hello World web part in the GAC. Right now red flags should be going off for any non-noob developer. This allows your code to operate with Full Trust (which is bad). To properly deploy a web part, it is necessary to set up Code Access Security. The problem is there are a ton of posts on this subject and there are a lot of different ways to do it. Unfortunately, none of them walk you through it fully or tell you which permissions you need for sure. When configured wrong, CAS has the ability to yellow screen your entire site. This post wll walk you through it and will get you started with a set of permissions. These may not be the best permissions but they are a good starting point from you and they are far from full trust.

In this sample, we will talk about a web part that needs basic SharePoint object model access. Most of this information, I got from reading stuff from Ted Pattison, but I'll explain what issues I ran into along the way. Let's assume we have a web part called MyWebPart and its in an assembly and namespace called MyAssembly. The first thing you have to do is build a feature to deploy your web part. Since this is a complete example, I will explain how to build the feature as well.

<Feature 
  Id="{BB1BAC21-7109-4a0c-822A-2554BE5C8E54}"
  Title="My Web Part Feature"
  Description="This web part does something."
  Hidden="FALSE"
  Scope="Site" 
  xmlns="http://schemas.microsoft.com/sharepoint/">
  <ElementManifests>
    <ElementManifest Location="elements.xml"/>
  </ElementManifests>
</Feature>

The key things to note here are the Feature Id which can be any guid (just make sure its unique within your app) and the reference to Elements.xml. This file defines what the feature is.

<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
  <Module Name="MyAssembly Web Parts" 
          List="113" 
          Url="_catalogs/wp" 
          Path="dwp" 
          RootWebOnly="true">
    <File Url="MyWebPart.webpart" Type="GhostableInLibrary" >
      <Property Name="Group" Value="My Web Part Group">
    </File>
  </Module>
</Elements>

The main thing of note here is the url to the .webpart file. This is a file that we have to create that tells SharePoint where to load the web part from. You can create this file manually or if somehow you already got your webpart installed in the system (i.e.: threw it in the GAC - don't leave it there), you can export it from the Web Part gallery. Here is a simple .webpart file.

<?xml version="1.0" encoding="utf-8"?>
<webParts>
  <webPart xmlns="http://schemas.microsoft.com/WebPart/v3">
    <metaData>
      <type name="MyAssembly.MyWebPart, MyAssembly, Version=1.0.0.0, 
Culture=neutral, PublicKeyToken=de279125eb887b23">
      <importErrorMessage>Cannot import this Web Part.</importErrorMessage>
    </metaData>
  <data>
    <properties>
      <property name="Title" 
type="string">QuickLinkCreator</property>
      <property name="Description" 
type="string">This web part does something.</property>
    </properties>
  </data>
 </webPart>
</webParts>

As you can see it relatively simple to create so if you can't export it basically you just need the assembly path. One thing to note is that there is a public key token. So be sure and create an snk to sign your project. This is all you need to create a feature. Usually the web part files will go in a folder such as TEMPLATE\FEATURES\\DWP. Although it doesn't really matter. This path corresponds to the directory structure of WSS.

Technically you could copy this out into your SharePoint FEATURES folder and install it. Of course it won't run because of CAS. So how do we request the necessary permissions? We create a solution package. This package alters the default wss_minimal policy for your particular DLL with the permissions it needs. Start by modifying AssemblyInfo.cs.

You will first need references to Microsoft.SharePoint.Security and System.Security.Permissions. Next add the following line. It allows for the assembly to run outside of Full Trust.

[assembly: System.Security.AllowPartiallyTrustedCallers()]

The last thing I recommend in your AssemblyInfo.cs is a static file version during development. The reason for this is that you have to specify the version number on your Safe Control statement later.

[assembly: AssemblyVersion("1.0.0.0")]

Next create a Solution folder in your project. In this folder, create a file called Manifest.xml. This file is big so I'll break it down into the key elements.

The root element Solution takes a SolutionId which is another GUID (make it different than your feature one). Next you tell it where to find the Feature.xml file for your web part and then you tell it the path to the actual .webpart file itself. You will need a TemplateFile element for each .webpart you have.

<Solution SolutionId="{5E5FD337-1E3E-47fe-B4BB-A08D70E74352}"
		  xmlns="http://schemas.microsoft.com/sharepoint/">

  <FeatureManifests>
    <FeatureManifest Location="MyProject\feature.xml">
  </FeatureManifests>

  <TemplateFiles>
    <TemplateFile 
Location="FEATURES\MyProject\DWP\QuickLinkCreator.webpart"/>
  </TemplateFiles>

As you probably know any assembly you call in SharePoint needs to be registered as safe. Instead of manually putting it in the web.config, you can add an entry to this file and it will be merged into the Web.config for you. Just specify the name of your assembly and the full path including public key token.

  <Assemblies>
    <Assembly DeploymentTarget="WebApplication" Location="MyAssembly.dll">
      <SafeControls>
        <SafeControl Assembly="MyAssembly, Version=1.0.0.0, Culture=neutral, 
	PublicKeyToken=de279125eb887b23" Namespace="MyAssembly" TypeName="*" Safe="True">
      </SafeControls>
    </Assembly>
  </Assemblies>

Now the fun part. Specifying Code Access Security. You will need to add a reference to Microsoft.SharePoint.Security to your project. Let me first show you the section and then I will attempt to explain why each one is required.

  <CodeAccessSecurity>
    <PolicyItem>
      <PermissionSet class="NamedPermissionSet" version="1" 
	Description="Permission set for MyAssembly.">
        <IPermission class="AspNetHostingPermission" version="1" 
	  Level="Minimal">
        <IPermission class="SecurityPermission" version="1" 
	  Flags="Execution, ControlPrincipal, ControlAppDomain,
                        ControlDomainPolicy,ControlEvidence,ControlThread">
        <IPermission class="Microsoft.SharePoint.Security.SharePointPermission, 
	  Microsoft.SharePoint.Security, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c"
          version="1" ObjectModel="True" Impersonate="True" UnsafeSaveOnGet="True" 
          Unrestricted="True">
        <IPermission class="System.Security.Permissions.EnvironmentPermission, 
          mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
          version="1" Read="UserName">
        <IPermission class="System.Security.Permissions.FileIOPermission, mscorlib, 
          Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1" 
          Read="$AppDir$" Write="$AppDir$" Append="$AppDir$" PathDiscovery="$AppDir$">
      </PermissionSet>
      <Assemblies>
        <Assembly Name="MyAssembly" >
      </Assemblies>
    </PolicyItem>
  </CodeAccessSecurity>

Here are the permissions I am using and have gotten to work. I believe some are still too much and as I tweak things I will update this post.

  • AspNetHostingPermission - This permission I believe is used to do any kind of basic ASP.NET function such as add controls to the page.
  • SecurityPermission - I picked this permission up from the working example I had. Mainly it specifies that the code is allowed to execute.
  • Microsoft.SharePoint.Security.SharePointPermission - This sets SharePoint security. Common parameters are ObjectModel = true which grants access to the ObjectModel. Impersonate is not documented. I only assume it has something to do with Imperonsating the current logged in user. UnsafeSaveOnGet allows access to the WSS database during HTTP-GET requests. Unrestricted = true allows the code to execute any SharePoint action. Certain API calls that modify security may require this .
  • System.Security.Permissions.EnvironmentPermission, mscorlib - This can be used to allow access to various environemnt settings. In this case, the username can be read.
  • System.Security.Permissions.FileIOPermission, mscorlib - You have to explicitly grant access to the file system even if you aren't doing any direct access with it. I got these settings with the use of $AppDir$ from another trust configuration file and it seems to work (previously was using unrestricted which is way bad). Also, if you have code that reads any thing from the file system (i.e.: a feature receiver reading an XML file from its folder), you have to grant access to that path.

If you are curious what the actual process does when you instal it, it makes a backup of your web.config (you'll find them in the same directory) and modifies it with the SafeControl elements you specified. It also changes the WSS_Custom trust element to a new configuration file which basically is named with a guid. This file has your custom permission set in it.

Wow, that was a lot. Does that mean we are done yet? Nope, of course not. Next you need to create a file to tell it what goes in the actualy deployment solution. .wsp files are what contain your actual binaries and web part configuratoin. These are actually just glorified cab files. Therefore you have to tell it what files go in the solution. Here is what a typical file would look like.

.OPTION EXPLICIT     ; Generate errors 
.Set CabinetNameTemplate=MyAssembly.wsp     
.set DiskDirectoryTemplate=CDROM ; All cabinets go in a single directory
.Set CompressionType=MSZIP;** All files are compressed in cabinet files
.Set UniqueFiles="ON"
.Set Cabinet=on
.Set DiskDirectory1=Package

; add the manfiest file itself
Solution\manifest.xml manifest.xml

; add the feature xml files
TEMPLATE\FEATURES\\feature.xml \feature.xml
TEMPLATE\FEATURES\\elements.xml \elements.xml

; add the webpart file - normally would be all online seperated by a space
TEMPLATE\FEATURES\\DWP\MyWebPart.webpart 
FEATURES\\DWP\MyWebPart.webpart

bin\Debug\MyAssembly.dlll MyAssembly.dll

Ok, so admittedly, I am not completely clear on the way some of the paths work. Basically it says get the file from path and put it in the specified destination in the wsp file. I am not realy questioning it, somehow it magically works when you install it.

Now we are getting close. We just need to make a deployment script. Ideally, you are developing directly on the WSS/MOSS server. If that's the case it makes deployment really easy. If not, you need to copy your project to the MOSS server first. To aid deployment, create a batch file. The batch file starts by making the wsp file with makecab. It then removes any exisitng solutions of the same name before adding and deploying the new solution. The operations listed in the batch file should seem pretty obvious.

makecab /f Solution\cab.ddf

@SET STSADM="c:\program files\common files\microsoft shared\
web server extensions\12\bin\stsadm"

%STSADM% -o retractsolution -name MyAssembly.wsp -immediate -allContentUrls
%STSADM% -o execadmsvcjobs
%STSADM% -o deletesolution -name MyAssembly.wsp -override
%STSADM% -o execadmsvcjobs

%STSADM% -o addsolution -filename package\MyAssembly.wsp
%STSADM% -o deploysolution -name MyAssembly.wsp -immediate -allContentUrls -allowCasPolicies
%STSADM% -o execadmsvcjobs

This installs the solution to all site collections (-allContentUrls). It is also possible to install to a particular site by specifying the url. Note, the parameter -allowCasPolicies which allows CAS to be specified when the solution is deployed.

After running your batch file, if all goes well, you will have a new feature installed. Activate it by going to the Features activation page. You will then see the web part, in the web part gallery. Preview it or add it to a page to start using it. If all goes well, you will not get any errors.

This process took me a long time to figure out. I hope this comes in handy for all friends jumping on the MOSS bandwagon (and anyone else).

Read the complete post at http://www.dotnettipoftheday.com/blog.aspx?id=371