Friday, October 28, 2011

Issue with Site Collection Admins, AD groups and GetUserEffectivePermissions in Sharepoint 2010

As you may know I have been working on a custom Request Access page for Sharepoint 2010. During the process of this I ran into an interesting issue related to using AD Groups for your Site Collection Administrators and what is returned by GetUserEffectivePermissions and DoesUserHavePermissions. To simulate you can do the following:
  1. Create a New Site or Site Collection.
  2. Create an AD Group that contains "Admin, User1" and "Admin, User2".
  3.  Add this AD Group under Site Actions > Site Permissions and then Site Collection Administrators.
  4. Add "Owner, User1" and "Owner, User2" to your owners group, and "Member, User1" and "Member User2" in your members group.
Now create a small web part of aspx page that loops through and lists people with FullMask. I use FullMask because we are migrating sites from 2007 which mucks the owners group and we cannot rely on end users to actually use the owners group. The code I have used is:

using (SPSite siteCollection = new SPSite(SPContext.Current.Site.ID))
{
     int userCnt = 0;
     SPUserCollection userCollection = siteCollection.RootWeb.SiteUsers;
     foreach (SPUser user in userCollection)
     {
          string userSTR = user.Name;
          string userPERM = siteCollection.RootWeb.GetUserEffectivePermissions(user.LoginName).ToString();
          if (userPERM.Equals("FullMask") && !user.IsSiteAdmin)
          {
               userCnt++;
               ownersOUTPUT.Text += userSTR + "</br>";
          }
     }
}
When you run this you should only see "Owner, User1" and "Owner, User2", which would be what you would expect. Now add "Admin, User1" and "Members, User1" to your owners group and reload. As expected, you should now see "Admin, User1" and "Member, User1" listed as well. Finally, remove "Admin, User1" and "Member, User1" from your owners group and refresh. You should see what I am talking about.

"Member, User1" should disappear like you would expect, but "Admin, User1" is still listed and will not go away. Notice my code excludes users where IsSiteAdmin is true. Try adding "Admin, User1" directly to Site Collection Administrators and reload. Poof, he is gone.

This is because when a user is added directly to Site Collection Administrators IsSiteAdmin returns true; however, when the user has access via an AD Group in the same location IsSiteAdmin returns false. I assume that this works properly before "Admin, User1" is added directly because the users permissions do not exist via the sharepoint table. However, when you add the user to the site a record is create and is not properly cleaned up when removed due to them being referenced as Site Collection Administrator via the AD Group.

I have not been able to test removing a user from the AD Group as that is just not a simple job in my environment. I have contacted our Microsoft Rep for more information on this, and will provide more information once I recieve it.

Wednesday, October 26, 2011

Custom Request Access page in Sharepoint 2010 - The Full Story

To share a quote from the most interesting man in the world... I don't code for Sharepoint often, but when I do, I prefer to know what the hell I am doing.

Update (06/06/2012): Before today the code that was here ran the possibility of generating a NullException error getting the current user. I have updated the code to resolve this based on information from Microsoft. For a descriptive addendum, see the update at the end of this post.

Recently I was tasked with customizing the Request Access page in Sharepoint 2010. Our group is small and our users not so saavy, so we simply wanted to list all the Site Owners on the Request Access page. This way end users would know who they needed to contact if they did not have access to a site. Slam dunk, right? Well, so I thought.

First off, I am by no means an expert Sharepoint or .NET developer. At the time of this writing, I have only developed a handful of Sharepoint applications and have under 6 months of experience. I have been a developer for a long time, but Sharepoint and .NET are not my native tongues. As such, I will explain as much as I understand and did to complete my tasks, but the why's of all of this will only be assumptions. Please feel free to educate me if I speak in error or you feel the need to clarify any of this.

I started out with a Google search, as all quests for knowledge usually do, and found a lot of links on how to do this. One of the most concise posts was by Anmol Rehan:

http://www.anmolrehan-sharepointconsultant.com/2011/08/how-to-use-custom-access-denied-page-in.html

One problem that is not well explained is that this creates a Site (Site Collection) scoped feature which will set the custom page. UpdateMappedPage() is not available to Sandboxed Solutions and must be a Farm solution, and sets the custom page at the WebApplication level. However, due to recent security fixes, you should get an error when trying to deploy or activate your solution.This is because webApp.Update() has to reach up into the WebApplication to work properly. As such, make sure you have this scoped at the WebApplication level and NOT the Site Collection level; Otherwise activating/deploying will give you an Access Denied error in your logs.

Another problem, my main problem and an issue with all the other information I found, is it only tell you how to configure Sharepoint to load a custom page. Anmol lists a simple 3 step process; however, step 2 was a lot more involved - at least for me - since this does not detail exactly on what you have to do to create these pages. I posted this question on Microsoft's forums (my post), but - as seems to be usual - I owned my own thread and had to figure it out myself. As my post started to be the number 1 hit when searching for my problem, I figured I should document this for others that might run into the same issue. This seems fairly common, but maybe I am just so green I am the only one that needed help. Either way, I hope this helps someone.

So look at Anmol's post and get started, when you get to step 2 "Create your costom Access Denied Page in Layouts" this is what you do.
  1. Right-click on your project in the Solution Explorer and pull out the menu for Add then click Sharepoint "Layouts" Mapped Folder.
  2. Right-click on the folder automatically created under the Layouts folder in your project then pull out the Add menu, click New Item and then select Application Page.
  3. Copy the contents original page into your custom one (these are located in the hive under TEMPLATE/LAYOUTS/) and go crazy. Easy right?
Well, not really. What I found is that if I just built my page then I would either get a 403 Forbidden error or got redirected to the Access Denied page. These types of pages are considers Safeguarded Application Pages, and appear to have some restrictions. First off, there can be issues with dynamic master pages and - I assume - other things that keep you from just laying into developing your custom page.

My first issue was I was really used to working with the code-behind; however, this only seemed to work if the Page directive Inherits itself. This also seemed to be the core reason for the 403 and Access Denied redirection. Once I changed this to inhert the original page - in my case Microsoft.SharePoint.ApplicationPages.RequestAccess - I could actually start seeing my changes. Of course, I could not use code-behind as any references to the page went all red and squiggly.

So you need to put all your functionality into the aspx page. Microsoft supported using code behind, but for these pages it seemed troublesome. This is a small bit of information, but this could have saved me a few days of work. I was never able to find any rules to what you could and could not do with these pages, and I would still love to know the hard facts. However, in the end I was able to get things working. We are still working around some issues related to using AD groups as Site Collection Administrators, which tends to list the AD group members as FullMask with the IsSiteAdmin equal to false, but I am hoping this is just an issue with our development environment.

To close up, this is the code I ended up using in my custom ReqAcc.aspx page:


Thanks to everyone for your support and help!

Update: For those that used the old version of the code (prior to 06/06/2012), here is the itemized changes:
  1. Replace SPUser reqUser = SPContext.Current.Web.CurrentUser; with string strUser = String.Format("{0}\\{1}", Environment.UserDomainName, Environment.UserName); and import the System namespace (<%@ Import Namespace="System" %>) You then need to convert this to a user object once you get into your RunWithElevatedPrivileges block with SPUser reqUser = thisroot.RootWeb.SiteUsers[strUser];. This is because the CurrentUser context was removed in these Safeguard Application pages as there was a bug allowing users to elevate privileges.
  2. I also now scope this as a WebApplication solution as restrictions were also put in place to disallow a Site Collection from updating the WebApplication. This makes sense, but limits us actually controlling this at a Site Collection.
  3. Added additional code I wrote for emailing site owners for access.

Monday, October 10, 2011

How I learned to love the Demon Butt

An interesting tale from my first 6 hours of Dark Souls

People that know me know video games are a big part of my life. I play a lot of games and enjoy punishing myself (and maximizing achievement hunting) by playing many of them on their hardest difficulty level: even though I hardly deserve to play there. Perhaps I just enjoy watching myself die... Who know?

Due to this perverse pleasure, I have been anxiously awaiting Dark Souls after being deprived Demon Souls as an Xbox user. It does not fail to disappoint as I get to see my deaths a thousand fold over any other game. What is surprising about Dark Souls is how it challenges all traditional thoughts of gaming, and flaunts of the old traditions at you. It barely contains any information about itself: in its manual, in its HUD or menu, nothing. Its as if you truly woke up in this foreign world with only your wits, and everything out there exists only to knock it the hell out of your skull. And this is how I began my first 6 hours of play...

The game starts like any other might: a cutscene, a cell, a corpse and a key then followed by a long corridor riddle with tutorial this and that's. At the end a seemly place of haven: a nice campfire and a large wooden door. I take a rest, ready my only weapon - little more than a broken sword hilt - and then pass through the door. Beyond I find a long, open room filled with jars but little else.

Hanging to the old convention of video games, I begin the fun of busting jars. I realized later Dark Souls does not give the typical reward from this destruction, but alas its still fun. I begin on the right of the room and noticed a ghost running around (Dark Souls representation of other players in other worlds). Immediately after a large demon plunges from the broken ceiling of the room, and kills me in a single blow. After my heart settles a moment, I laugh and remind myself I was expecting a hard game.

What followings is a painful string of moments that dragged my soul through several painful rungs of frustration, sorrow and crushingly utter defeat. I pummeled myself at this demon hundreds of times. I remade characters, altered strategies, searched on line for tactics and nearly drove myself mad trying to beat this thing. I could find no alternate paths except to this room, this altar of death manned by this huge, fat demon who swatted me like a fly.

To make matter worse, my wife would look over my trials and burst into laughter. My best tactic was to get directly behind and under this demon which filled the entire screen with this monstrous, flabby demon butt. However, this tactic rewarded me to getting his health down to 3/4ths and seemed the only path to ridding the world of this horrible creature. So a hundred more times I ran this gambit until tears streamed down my face and I felt utter soul crushing defeat. I am not one to give up, but I saw no fairness in me facing up against this demon with a broken weapon.

So 6 hours in and only a few feet into the game, I deepened my Google searches to find only one site (this was on the release day) that mentioned this fight. I watched the video as the demon dropped and the guy ran past it into a small door in the corner to get away. I began laughing harder than I ever had before.

I believe the developer placed that pretty room with all those jars and a big demon knowing the traditional gamer would become fall into old habits and smash all things large and small. However, the proper tactic was to run away and find your way out. I was so distracted by the thought of this being a tutorial for fighting, since it was the first real enemy, but no sir - this was a far more humbling lesson: in Dark Souls running should always be your first choice.

There are a lot of good things to say about Dark Souls, but this is the thing that is hardest to explain. This game challenges typical game mechanics which have taught us how to play games. I have learned my lesson and now understand enough about this world to survive (barely). I think most of the difficulty comes down to this: don't trust your preconceptions of how to play this game. Be careful and cherish life: because even death and dying are a large part of the game.