17 January 2008

Access Denied when using SPSite.AllWebs

As I have been working to develop a web part that our organization will use to present roll-up data from multiple sub-sites to a top-level site, I've come across a n interesting tid bit that was frustrating at first.

It is the use of SPSite.AllWebs property. Check this code:

            ArrayList Lists = new ArrayList();
            SPSite CurrentSite = SPContext.Current.Site;
            SPContentTypeId ContentType = new SPContentTypeId(PARENT_CONTENT_TYPE);
            SPWebCollection UserSites = CurrentSite.AllWebs;

            foreach (SPWeb web in UserSites)
            {
                foreach (SPList list in web.Lists)
                {
                    if (list.ContentTypesEnabled)
                    {
                        try
                        {
                            bool ContainsType = false;
                            SPContentTypeCollection Types = list.ContentTypes;
                            foreach (SPContentType Item in Types)
                            {
                                if (Item.Id.IsChildOf(ContentType))
                                {
                                    ContainsType = true;
                                    break;
                                }
                            }
                            if (ContainsType)
                            {
                                Lists.Add(list);
                            }
                        }
                        catch
                        {
                        }
                    }
                }

When running as an administrator, this code doesn't cause a single problem. However, if you swap to a non-administrative user that doesn't have full control of the site, you'll get the lovely access denied page. The problem is that you must have full control of the site to be able to use the AllWebs property.

The fix for this is to use a property of the SPWeb object. See the corrected code below:

            ArrayList Lists = new ArrayList();
            SPSite CurrentSite = SPContext.Current.Site;
            SPContentTypeId ContentType = new SPContentTypeId(PARENT_CONTENT_TYPE);
            SPWebCollection UserSites = CurrentSite.OpenWeb().GetSubwebsForCurrentUser();

            foreach (SPWeb web in UserSites)
            {
                foreach (SPList list in web.Lists)
                {
                    if (list.ContentTypesEnabled)
                    {
                        try
                        {
                            bool ContainsType = false;
                            SPContentTypeCollection Types = list.ContentTypes;
                            foreach (SPContentType Item in Types)
                            {
                                if (Item.Id.IsChildOf(ContentType))
                                {
                                    ContainsType = true;
                                    break;
                                }
                            }
                            if (ContainsType)
                            {
                                Lists.Add(list);
                            }
                        }
                        catch
                        {
                        }
                    }
                }

By using the GetSubwebsForCurrentUser() method, I'm able to get a list of subsites that the user does have access to view. If the user doesn't have access to any subsites, there will not be the access denied error message and you can display a warning to the user.

8 comments:

Eric Shupps said...

Be very careful with the use of both GetSubwebsForCurrentUser() and SPSite.AllWebs(). These two OM methods are both performance killers on large sites (200+ subsites). Instead, use the PortalSiteMapProvider methods described in Steve Peschka's white paper (http://go.microsoft.com/fwlink/?LinkId=95450&clcid=0x409). You may not see much performance impact now, but you'll thank Steve later when your site grows up and your code starts to sputter and die...

Chris said...

Eric,

Thank you so much for this tip. You've added to my reading list!

Matthew said...

I spent an entire 8-hour shift looking for a way to use the AllWebs without throwing that error. Elevated permissions with impersonation of the current user refused to work unless the current user is a site collection owner. I just wanted to thank you for posting this finding.

Lars Nielsen said...

Thanks a lot for this tip - I never would have figured this out!

Paul Matthews said...

I've tried to use GetSubwebsForCurrentUser() instead of AllWebs, but the problem is that it misses out the current site.
On my site if I call AllWebs, I get a collection of 7 sites. Topsite, plus 6 subsites. When I call GetSubWebForCurrentUser() it returns a collection of 6 sites, it doesn't include the top site. I need to iterate through all 7 sites, how could I acheive this? I thought about using SPWebCollection.Add to add my missing site to the collection, but that isn't right is it?

jamesqua said...

Eric, it is my understanding that GetSubwebsForCurrentUser() isn't recursive, so it wouldn't be that much of a performance killer unless all 200 of your subsites were directly underneath the calling site.

Anonymous said...

The linked white paper from Steve Peschka only covered list performance, and did not seem to mention AllWebs or GetSubwebsForCurrentUser, or indeed, webs in any form. Also, it said that PortalSiteMapProvider may not be used from WinForm applications.

applicationdevelopment said...

I had a similar issue with a custom build site. Users were getting access denied on the main page, but could browse the rest of the site. As soon as we added the user to site owners group they could see the page...

The developer had a dynamic menu that was different depending of a user is a site owner or not...

We fixed the problem by letting user view the membership of the "Site Owners" group. The web part was trying to see if the user that was accessing the site is a site owner, and since the user did not have rights to see who is in that group we got the access denied. It's the second time i bump into this issue - and the second time I end up spending a week troubleshooting this. Hopefully this will help somebody else.