Friday, December 10, 2010

Content Link Web Part for SharePoint

The Puzzlepart Content Link web-part works like the Content Editor web-part in ContentLink mode, but does not require you to configure anonymous access on source web-applications for cross site-collection linked content URLs. ContentLink is useful for showing common content stored in a central document library for use across other site-collections and sites.

Use the ContentLink property to set a server relative or absolute URL of the linked content to show. The user must of course have at least read-access to the document library where the linked content is stored. Note that the web-part is not suitable for solution gallery/sandboxed deployment due to the use of web request.

The web-part will work for retrieving linked content from other web-applications and site-collections even if anonymous access is not turned on. It also works when anonymous access is turned on, when the anonymous web-application policy is "no policy", "deny write" or "deny all". The code use the default credentials of the user when making the web request.

Download the WSP package or just the C# source for the web-part under Source Code at CodePlex.

Monday, November 22, 2010

Managing Memberships, Colleagues and Links for Other SharePoint User Profiles

Programming the SharePoint 2010 UserProfileManager, ColleagueManager, QuickLinkManager and MembershipManager seems simple enough, until you try to manage these profile aspects for other users. There are several quirks that when forgotten throws an UnauthorizedAccessException with the message "This operation requires you to be managing your own data or have administrator privileges".

Note that using the well known RunWithElevatedPrivileges is not sufficient to read the profile data of other users. To be able to do that, your UserProfileManager code must either run as a user profile manager, or be configured to respect the built-in privacy control using the IgnoreUserPrivacy parameter. Note how the SDK is very vague on using true or ...hmm... true?
When this parameter is true, administrators can load and access complete user records for any user profile objects. When this parameter is true, administrators work as regular users and can load only trimmed profile information based on the user profile’s privacy setting.
As it turns out, you must use false to get the expected behavior to avoid getting the access denied exception as a normal user. Only accounts that are registered as User Profile service application (UPA) managers will have access to ignore user privacy. If your code use IgnoreUserPrivacy = true and runs as an account that is not an UPA administrator, you will get this error:
You must have manage user profiles administrator rights to use administrator mode.

Set IgnoreUserPrivacy to false when creating the UserProfileManager object, as shown here:

//profile and membership internally use ValidateIsSelfOrAdmin, must use IgnoreUserPrivacy = false
string siteUrl = SPContext.Current.Site.Url;
using (SPSite mySite = new SPSite(siteUrl))
{
SPServiceContext context = SPServiceContext.GetContext(mySite);
UserProfileManager profileManager = new UserProfileManager(context, false);

MySiteUtilities profileSiteInfo = new MySiteUtilities(Page);
UserProfile userProfile = profileManager.GetUserProfile(profileSiteInfo.CurrentProfileSiteAccountName);

MembershipManager memberManager = userProfile.Memberships;
Membership[] membershipList = memberManager.GetItems(MembershipSortType.Alphabetical);

. . .
//membership management code here
. . .

}

If you need to ignore the user privacy to filter memberships or access private profile properties, or manage user profiles in general, then you must be an UPA administrator or the code must implement Windows impersonation as the user profile manager ignores RunWithElevatedPrivileges.

Note that SharePoint site memberships are periodically processed by the "User Profile Service Application Proxy - User Profile to SharePoint Full/Quick Synchronization" timer jobs, and that only direct account membership in a site's member SharePoint group will be registered in the user profile membership list. Having access to a site through an AD security group will not do, as SharePoint don't resolve AD group memberships.

Wednesday, October 06, 2010

Secure Store Service: Create Site-Collections avoiding UnauthorizedAccessException

The MSDN forums is full of questions on how to create a new site-collection from code, especially on what permissions are required to avoid the UnauthorizedAccessException "Access is denied" HRESULT: 0x80070005 error. Your code might work fine when running as you on your development server, and of course under the identity of the SharePoint farm account; but not when running under another application pool identity, or as a normal end-user or even as the SHAREPOINT\System account in workflows or event receivers using SPSecurity.RunWithElevatedPrivileges.

The simple answer is that CreateSite requires the same permissions as the SharePoint farm account. You certainly do not want to give all your end-users those permissions, so typically you would do a revert-to-self using RunWithElevatedPrivileges and run the site provisioning code as your SharePoint web-application application pool identity. Alas, adding the app-pool identity to the farm administrator group, giving it "full control" and "run as system" in web-app User Permission Policy, adding it to server local groups such as WSS_WPG or even to SQL Server db_owner and WSS_Content_Application_Pools database roles in the config and content databases, will not provide a configuration that works across all scenarios from web-parts to event receivers, workflows and integration tests.

Always try to assign a managed service account for the SP web-app application pool identity using Central Admin first or run GrantAccessToProcessIdentity in PowerShell. Still, getting access to the content database might not be enough for creating new site-collections, access to the configuration database is also needed for the process account.

The simple solution to this problem is to run all SharePoint web-applications using the farm account. This is definitely not recommended, and the Central Admin Health Analyzer will report the "The server farm account should not be used for other services" error in the security category.

A better alternative is to do a real RevertToSelf using WindowsImpersonationContext rather than the SharePoint RunWithElevatedPrivileges abstraction. This will require that your code knows the login name and password of the farm account. It is not best practice to store these in clear text in your code or config files, so I recommend using the SharePoint 2010 Secure Store Service to provide the farm account credentials to your code. Here's the code to implement impersonation in ASP.NET and the code to get credentials from the default secure store provider to get you started.


Use a Secure Store target application of type "Group" to store the Windows credentials of the farm account. Add only the required app-pool identities as target application "Members" when setting the target application access permissions, and then use RunWithElevatedPrivileges when retrieving the farm account credentials from the Secure Store Service.

So the final solution is this: retrieve the farm account credentials in your code using the app-pool identity, impersonate the farm account using those credentials, and then create a new site-collection from your code.

Note that there are also issues with claims-based authentication (CBA) when adding sites from code. The known workaround is to enable self-service site creation (SSSC) and use SelfServiceCreateSite from code.

Need to provision lists, pages and other stuff to your new site? Check out the SharePoint Site Configurator Feature over at CodePlex. Its a great resource for learning how to configure SharePoint sites.

Tuesday, September 28, 2010

Customizing the Search Action Links in SharePoint 2010

The SharePoint 2010 Search Action Links web-part is really just the Search Core Results web-part with the 'Show search results' settings set to false. Some of the action links have configurable captions such as the 'Show more results' link, while the 'Alert me' and 'RSS feed' link captions are not exposed in the web-part settings. However, it is quite easy to customize the layout, styling, links and content of the Search Action Links web-part - you can even add custom action links.

This example shows a searchable site directory built from customized standard SharePoint 2010 search web-parts:


Customizing the Search Action Links web-part is done exactly like for the Search Core Results web-part: you customize the embedded XSL or provide a custom XSL file using the hidden XslLink setting in the .webpart file. The following example shows how to customize the caption of the alerts and RSS links:


The example XSL shows how to set an <xsl:variable> field based on different conditions using <xsl:choose>. XSL don't have variables like in programming languages; they behave like C# const or read-only fields, they cannot be changed when first set.

The example XSL also shows how to fix an error with the RSS feed link when using a 'Fixed query' in your search page, or when using a specific search 'Scope' and no user query keyword by default, to show all results from that scope. The RSS link do not pick up these settings from the web-part, and the query RSS feed will generate the "Search RSS feed generation failed" error message. This error is caused by there being no keyword, which is fixed by adding a keyword to the URL using the k= querystring key. Just add the fixed query or the scope as the keyword:

/_layouts/srchrss.aspx?k=scope:"Puzzlepart"

Note that the 'Alert me' link don't have the k= RSS feed issue, rather it uses JavaScript and the hidden <input name="P_Query"> field on the page. This field is emitted by the Search Core Result web-part and contains the complete keyword query including the user query and the Scope, FixedQuery and AppendedQuery property settings.

Add custom parameters to the XSL when you need to have extra web-part configuration options beyond the provided standard parameters. For example, to simulate the P_Query mechanism, pass the query keywords as custom parameters. The example XSL shows how to use the custom parameter, along with the standard parameters. Use the 'Parameters Editor' in the web-part tool pane to configure your custom web-part setting:

<ParameterBinding Name="AppendedFilter" Location="None" DefaultValue="scope:&qout;Puzzlepart&qout;" />

Pass your configuration value using the DefaultValue attribute, using "None" as the Location. You can of course also set the 'alert me' and 'RSS feed' link captions using parameters.

I use code like this in a FeatureActivated event receiver to configure and add the customized web-part to our custom web part pages:

private static void AddWebPartResultsActionLinks(SPLimitedWebPartManager lwpm, string title, string siteTemplate)
{
    string xslParams = "";
    if(!String.IsNullOrEmpty(siteTemplate))
        xslParams = String.Format("<ParameterBinding Name='AppendedFilter' Location='None' DefaultValue='SiteInfoSiteTemplate:\"{0}\"' />", siteTemplate);

    CoreResultsWebPart ralwp = new CoreResultsWebPart()
    {
        Title = title,
        ChromeType = PartChromeType.None,
        QueryNumber = QueryId.Query1, //must be UserQuery because user can search the SiteInfoScope
        ShowActionLinks = true,
        ShowSearchResults = false, //this makes it an "action links" web-part
        ShowWindowsSearch = false,
        DisplayRSSLink = true,
        DisplayAlertMeLink = true,
        XslLink = "/CenterPages/XSL/SearchActionLinks.xsl",
        ParameterBindings = xslParams,
        UseLocationVisualization = false,
        ShowMessages = false
    };
    lwpm.AddWebPart(ralwp, "MiddleRightZone", 1);
}


You can of course export the customized Search Action Links web-part and use <AllUsersWebPart> in a file module to provision the web-part to a page, but the elements.xml file quickly gets huge and unmanageable. In addition, you'll have to encode any CDATA sections embedded in the exported web-part XML, as CDATA sections cannot be nested. That's why I recommend using XslLink rather than embedded XSL, and then code to provision the customized web-parts.