In previous posts, I mentioned that I would show you how to extend Solution 1 and 2 by somehow merging them into an existing authentication provider and finally packaging everything into a feature called “Guest Account Enabler”. An obvious benefit to creating a feature is that it makes it optional to have the guest account functionality in your site. In this post and for the sake of brevity, I assume that Internet Zone (Protected by FBA) is the zone that you want to have the Guest Account enabled , but nothing prevents you from changing the code below to extend it to other zones using the same authentication provider as Internet zone.
If you haven’t already read Part1 and Part 2 , here are the links:
The first step is to create our ‘Global.asax’ file. While our ‘Global.asax’ is very similar to the ones shown in Solution 1 and 2 in part 2, it has one important difference. It constructs the ‘Guest’ account only if the relevant feature is activated. Here is the code for new ‘Global.asax’.
[csharp]
<%@ Assembly Name="Microsoft.SharePoint"%>
<%@ Application Language="C#" Inherits="Microsoft.SharePoint.ApplicationRuntime.SPHttpApplication" %>
[/csharp]
Only one thing needs to be highlighted here: Membership.ValidateUser(“Guest”, “”). I added this condition to ‘Global.asax’ to check to see if the feature is activated or not. As you will see later in this post , ValidateUser() method of our custom authentication provider will return False if the corresponding feature is not activate. As such “Guest Account” security context (FBA token) won’t be constructed for anonymous users in FormsAuthentication_OnAuthenticate and annonymous users will continue to their journey in your site as an unnamed identity as before.
With the ‘Global.asax’ properly coded , the next step is create the a Feature. All Features must contain a Feature definition file, so go ahead and add a new XML file to your project named Feature.xml and add the following code to the file.
[xml]
[/xml]
Well, our feature declares a FeatureReciever that needs to be coded to take care of some deployment tasks for us. This includes copying our custom ‘Global.asax’ file (declared as ElementFile in ElementManifests node) to the root directory of site that servers incoming traffic from internet (Internet Zone). Here is the FeatureActivated method:
[csharp]
public override void FeatureActivated(SPFeatureReceiverProperties properties)
{
SPSite site = properties.Feature.Parent as SPSite; // get site reference
SPWebApplication webApp = site.WebApplication as SPWebApplication;
foreach (SPUrlZone zone in webApp.IisSettings.Keys)
{
if (zone == SPUrlZone.Internet)
{
// The settings of the IIS application to copy global.asax file to.
SPIisSettings oSettings = webApp.IisSettings[zone];
// Determine the source and destination path
string sourcePath = string.Format(@”{0}\FEATURES\{1}\”, SPUtility.GetGenericSetupPath(“Template”),@”\GuestEnablerFeature” );
string destPath = oSettings.Path.ToString();
File.Copy(Path.Combine(sourcePath, “global.asax”), Path.Combine(destPath, “global.asax”), true);
}
}
}
[/csharp]
With the shell of our Feature created , the next step is going to the actual Authentication Provider code and change following overridden methods to support “Guest Account” capability for anonymous users.
- ValidateUser
- GetUser
- GetAllUsers
- FindUsersByName
[csharp]
public override bool ValidateUser(string username, string password)
{
//Check to see if Guest Enabler Feature is installed and activated
bool showGuestAccount = Utils.IsSiteFeatureActivated(HttpContext.Current, “602D09E4-4F0D-4058-951D-55059BA87943”);
if (username == “Guest” && showGuestAccount) return true;
//the rest of your code to validate non-guest users goes here
}
[/csharp]
[csharp]
public override MembershipUser GetUser(string username, bool userIsOnline)
{
//Check to see if Guest Enabler Feature is installed and activated
bool showGuestAccount = Utils.IsSiteFeatureActivated(HttpContext.Current, “602D09E4-4F0D-4058-951D-55059BA87943”);
return Utils.GetUserByFilter(this.Name,this.ConnectionString,Utils.UsersFilter.UserName,username,showGuestAccount);
}
[/csharp]
[csharp]
public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
{
//Check to see if Guest Enabler Feature is installed and activated
bool showGuestAccount = Utils.IsSiteFeatureActivated(HttpContext.Current, “602D09E4-4F0D-4058-951D-55059BA87943”);
return Utils.GetUsersByFilter(this.Name,this.ConnectionString,Utils.UsersFilter.Empty,null,pageIndex,pageSize,out totalRecords,showGuestAccount);
}
[/csharp]
[csharp]
public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
{
//Check to see if Guest Enabler Feature is installed and activated
bool showGuestAccount = Utils.IsSiteFeatureActivated(HttpContext.Current, “602D09E4-4F0D-4058-951D-55059BA87943”);
return Utils.GetUsersByFilter(this.Name,this.ConnectionString,Utils.UsersFilter.UserName,usernameToMatch,pageIndex,pageSize,out totalRecords, showGuestAccount);
}
[/csharp]
Three things need some explanation in the preceding code snippets.
- I have used couple helper methods in the above code snippets which is included below.
- You need to virtually construct the ‘Guest Account’ as a MembershipUser object in GetUser() when this method is called.
- You need to add the ‘Guest Account’ to the MembershipUserCollection in GestAllUsers() and FindUsersByName() so “People Picker” can resolve that account for targeting content or assigning permission purposes.
[csharp]
public class Utils
{
public enum UsersFilter { Email, UserName, Empty }
public static bool IsSiteFeatureActivated(HttpContext context,string featureID)
{
SPWebApplication app = SPWebApplication.Lookup(new Uri(context.Request.Url.AbsoluteUri));
using(SPSite site = app.Sites[0])
{
SPFeature returnsiteFeatures = site.Features[new Guid(featureID)];
return (returnsiteFeatures != null ? true : false);
}
}
//Create a MembershipUserCollection consisting of our single user.
public static MembershipUserCollection AddAnnonUserToMembershipCollection(string procviderName, MembershipUserCollection users)
{
users.Add(new MembershipUser(procviderName, “Guest”,
“Guest”, string.Empty, string.Empty, string.Empty, true, false,
DateTime.MinValue, DateTime.MinValue, DateTime.MinValue,
DateTime.MinValue, DateTime.MinValue));
return users;
}
public static MembershipUserCollection GetUsersByFilter(string procviderName, string connectionString, UsersFilter filter, string value, int pageIndex, int pageSize, out int totalRecords, bool includeGuestUser)
{
MembershipUserCollection resultUsers;
// Code to get All users based on the filer specified and populate resultUsers collection goes here (removed for code brevity)
//Check to see if you need to add Guest account to the collection
if (includeGuestUser)
{
totalRecords += 1;
return AddAnnonUserToMembershipCollection(procviderName, resultUsers);
}
else
return resultUsers;
}
public static MembershipUser GetUserByFilter(string providerName, string connectionString, UsersFilter filter, string value, bool includeGuestUser)
{
if (value == “Guest” && includeGuestUser)
return new MembershipUser(providerName, “Guest”, “Guest”, string.Empty, string.Empty, string.Empty, true, false, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue, DateTime.MinValue);
//rest of your code to get non-guest user goes here
}
}
[/csharp]
Once you have created all of the required pieces and deployed your feature in a solution package (not explained in this post), then you will see that our Feature in the Features collection as shown below:
Go ahead and activate the feature. You will notice that the ‘Global.asax’ of your internet zone is replaced with your own custom one and subsequently any anonymous calls will be executed under the security context of our virtual ‘Guest Account’. If you disable the feature everything goes back to normal life.
| Feature is activated |
 |
| |
| Guest Account is resolved in all zones |
 |
| |
| People Picker shows Guest Account |
 |
| |
| Feature is deactivated |
 |
| |
This pretty much concludes my three-part series on Anonymous Users in SharePoint. Hope you have found them useful.