Thursday, May 14, 2009

Enumerate Role Assignments to retrieve Groups and Users Permissions - Generating a complete report

Introduction:

The following SharePoint Application Page with C# in line code enumerates all the roles assignments of a Windows SharePoint Services 3.0 or MOSS site collection and displays for each web site:
  • The role member and reports if it is a Group or an User.
  • If it is a group, displays the number of users in the Group and the users list.
  • If it is a group, displays the permissions for all significant Site Collection elements (if inheritance was broken for a List, a Folder or an Item, this will be specified). It is information at a cross-site level.
  • In any case (User or group), displays the Roles list for the current web site.
  • In any case (User or group), displays for each previous roles, the Roles Permissions list.
This post and its code sample is an improvement of the previous post Enumerate Role Assignments to retrieve Groups and Users Permissions in a Windows SharePoint Services 3.0 or MOSS Site . I also did this new post to help a reader that has asked me information about SharePoint Roles and Permissions reporting.
Why to use it?

In Windows SharePoint Services 3.0, access to Web sites, lists, folders, and list items is controlled through a role-based membership system by which users are assigned to roles that authorize their access to Windows SharePoint Services objects.

To give a user access to an object, you can do so either by adding the user to a group that already has permissions on the object, or by creating a role assignment object, setting the user for the role assignment, optionally binding the role assignment to the appropriate role definition with base permissions, and then adding the assignment to the collection of role assignments for the list item, folder, list, or Web site. If you do not bind the role assignment to a role definition when assigning a user to a role, the user has no permission.

As there is two ways of granting permissions to a specific user, this can easily lead to a lack of organization regarding Security Granting Policy, and you may have some SharePoint sites to clean up.

When you want to check the users and the groups present in a Site Collection web sites and their role, it can take time doing it by browsing "People and Group" administration pages for each web site. It would be nice to display all the information in a single report. The following code sample will give you this kind of report, and it will be easier for you to reorder Users and Groups using it.

How to use it?

Copy the following code in an .aspx file.
Paste the file in the LAYOUTS directory.
"C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\LAYOUTS"

Browse the page with site administrator permissions from any site using the usual Application Page url (If you have named the aspx page enumerateroles.aspx access it via url ...myWebSite/_layouts/enumerateroles.aspx).
Check Report in the page.

Code of the Role Assignments Report Application Page:


<%@ Assembly Name="Microsoft.SharePoint.ApplicationPages, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Assembly Name="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Assembly Name="Microsoft.SharePoint.Security, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
<%@ Register TagPrefix="SharePoint" Namespace="Microsoft.SharePoint.WebControls"
    Assembly="Microsoft.SharePoint, Version=12.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" %>
 
<%@ Page Language="C#" MasterPageFile="~/_layouts/application.master" %>
 
<%@ Import Namespace="Microsoft.SharePoint" %>
<%@ Import Namespace="Microsoft.SharePoint.Utilities" %>
<%@ Import Namespace="System.Security.Permissions" %>
<%@ Import Namespace="Microsoft.SharePoint.Security" %>
<%@ Import Namespace="System.Diagnostics" %>
<asp:Content ID="Content1" ContentPlaceHolderID="PlaceHolderMain" runat="server">
    <asp:Label ID="lblOutPut" runat="server" />
    <asp:Panel ID="pnlHidden" runat="server" />
 
    <script type="text/javascript" language="JavaScript">
 
        document.getElementById('<%=pnlHidden.ClientID %>').style.display = 'none';
 
        function Toggle(node) {
            // Unfold the branch if it isn't visible
            if (node.nextSibling.style.display == 'none') {
                // Change the image (if there is an image)
                if (node.children.length > 0) {
                    if (node.children.item(0).tagName == "IMG") {
                        node.children.item(0).src = "/_layouts/IMAGES/collapseminus.gif";
                    }
                }
                node.nextSibling.style.display = '';
            }
            // Collapse the branch if it IS visible
            else {
                // Change the image (if there is an image)
                if (node.children.length > 0) {
                    if (node.children.item(0).tagName == "IMG") {
                        node.children.item(0).src = "/_layouts/IMAGES/collapseplus.gif";
                    }
                }
                node.nextSibling.style.display = 'none';
            }
        }
    </script>
</asp:Content>
 
<script runat="server">       
 
    string htmlOutput = "";
 
    protected string WriteIsRootWeb(SPWeb aWeb)
    {
        if (aWeb.IsRootWeb)
        {
            return " (This is the Site Collection Root Web)";
        }
        else
        {
            return "";
        }
    }
 
 
    [SharePointPermission(SecurityAction.Demand, ObjectModel = true)]
    protected override void OnPreRender(EventArgs e)
    {
        base.OnPreRender(e);
        using (SPSite mySite = SPContext.Current.Site)
        {
            string siteUrl = mySite.Url;
            this.Page.ClientScript.RegisterClientScriptBlock(base.GetType(), "GroupPermissionCallback", "\r\nfunction WebForm_DoCallback(controlId,url,GroupPermissionCallback,ctx,unknownNullValue,unknownBooleanvalue)\r\n{\r\n    var strUrl ='" + siteUrl + "/'+ url;\r\n    open(strUrl, '_blank');\r\n}\r\n\r\n", true);
        }
    }
 
    public override void VerifyRenderingInServerForm(Control aControl){}
 
    protected override void Render(HtmlTextWriter writer)
    {
        bool isAgroup = true;
        SPGroup aGroup = null;
 
        foreach (SPWeb aWeb in SPContext.Current.Site.AllWebs)
        {
            htmlOutput += "\n<br>******************************************";
            htmlOutput += "\n<br><span style='color:blue'>Roles Assignments Report on web site " + aWeb.Title + WriteIsRootWeb(aWeb) + "</span>";
            htmlOutput += "\n<br>******************************************<br>";
 
            htmlOutput += "\n<br><div style='padding-left:40px'>List of " + aWeb.Title + " Groups";
 
            foreach (SPGroup Group in aWeb.Groups)
            {
                htmlOutput += "\n<br>" + Group.Name + " ID: " + Group.ID;
            }
 
            htmlOutput += "\n</div>";
            htmlOutput += "\n<div style='padding-left:20px'>";
            foreach (SPRoleAssignment aRole in aWeb.RoleAssignments)
            {
                isAgroup = true;
                htmlOutput += "\n<br>*************<br>";
                try
                {
                    aGroup = aWeb.Groups.GetByID(aRole.Member.ID);
                }
                catch
                {
                    isAgroup = false;
                }
 
                if (isAgroup)
                {
                    htmlOutput += "\n<br><span style='color:#357EC7'>Group Id : " + aRole.Member.ID.ToString() + " | " + " Principal Name : " + aRole.Member.Name + "</span>";
 
                    int numberOfusers = aWeb.Groups.GetByID(aRole.Member.ID).Users.Count;
                    htmlOutput += "\n<br><br>Number of users:" + numberOfusers;
                    aGroup = aWeb.Groups.GetByID(aRole.Member.ID);
                    htmlOutput += "\n<br>";
                    if (numberOfusers > 0)
                    {
                        htmlOutput += "\n<br>List of " + aGroup.Name + " users";
 
                        foreach (SPUser aUser in aGroup.Users)
                        {
                            htmlOutput += "\n<br> - " + aUser.Name;
                        }
                    }
 
                    GroupPermissions myGroupPerm = new GroupPermissions();
                    myGroupPerm.GroupId = aRole.Member.ID;
 
                    System.IO.StringWriter myStrWriter = new System.IO.StringWriter();
                    HtmlTextWriter myWriter = new HtmlTextWriter(myStrWriter);
 
                    pnlHidden.Controls.Add(myGroupPerm);
 
                    myGroupPerm.GroupId = aRole.Member.ID;
                    myGroupPerm.RenderControl(myWriter);
 
                    htmlOutput += "<br><br><a onClick='Toggle(this)'><IMG style='text-decoration:none;border:0px' SRC='/_layouts/IMAGES/collapseplus.gif' /><span style='cursor:hand'>All (cross-sites) Permissions for " + aRole.Member.Name + "</span></a><div style='width:98%;display:none;'>" + myStrWriter.ToString() + "</div>";
                    htmlOutput += "\n";
                }
                else
                {
                    htmlOutput += "\n<br><span style='color:#3BB9FF'>User Id : " + aRole.Member.ID.ToString() + " | " + " Principal Name : " + aRole.Member.Name + "</span>";
                }
                htmlOutput += "\n<br><br>role(s) for " + aRole.Member.Name + " in " + aWeb.Title + ": <br>";
                foreach (SPRoleDefinition aRoleDefBinding in aRole.RoleDefinitionBindings)
                {
                    htmlOutput += "\n<br> - " + aRoleDefBinding.Name + "   (" + aRoleDefBinding.Description + ")";
                    htmlOutput += "\n<div style='padding-left:10px;'>List of permissions for " + aRoleDefBinding.Name + ":";
                    htmlOutput += "\n<br>" + aRoleDefBinding.BasePermissions.ToString();
                    //htmlOutput += "\n\n" + aRole.RoleDefinitionBindings.Xml + "\n\n";//to see the xml from View Source of the page
                    htmlOutput += "\n</div>";
                }
 
                htmlOutput += "\n<br>";
            }
            htmlOutput += "\n</div><br>";
        }
 
        htmlOutput += "\n<br>*************<br>";
 
        lblOutPut.Text = htmlOutput;
 
        base.Render(writer);
    }
</script>
 



7 comments:

est said...

Hi Marc,
thanks for the post. Really appreciate that. I went to try it out but hit error :

The file '/_layouts/thisPage.aspx' does not exist.
at System.Web.UI.Util.CheckVirtualFileExists(VirtualPath virtualPath)
at System.Web.Compilation.BuildManager.GetVPathBuildResultInternal(VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile)
at System.Web.Compilation.BuildManager.GetVPathBuildResultWithNoAssert(HttpContext context, VirtualPath virtualPath, Boolean noBuild, Boolean allowCrossApp, Boolean allowBuildInPrecompile)
at System.Web.Compilation.BuildManager.GetVirtualPathObjectFactory(VirtualPath virtualPath, HttpContext context, Boolean allowCrossApp, Boolean noAssert)
at System.Web.Compilation.BuildManager.CreateInstanceFromVirtualPath(VirtualPath virtualPath, Type requiredBaseType, HttpContext context, Boolean allowCrossApp, Boolean noAssert)
at System.Web.UI.PageHandlerFactory.GetHandlerHelper(HttpContext context, String requestType, VirtualPath virtualPath, String physicalPath)
at System.Web.UI.PageHandlerFactory.System.Web.IHttpHandlerFactory2.GetHandler(HttpContext context, String requestType, VirtualPath virtualPath, String physicalPath)
at System.Web.HttpApplication.MapHttpHandler(HttpContext context, String requestType, VirtualPath path, String pathTranslated, Boolean useAppConfig)
at System.Web.HttpApplication.MapHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
Troubleshoot issues with Windows SharePoint Services.

I copied the codes and paste it in default.aspx(source view) and rename it to myPage.aspx. Then pasted it in C:\Program Files\Common Files\Microsoft Shared\Web Server Extensions\12\TEMPLATE\Layouts.

And access via http://siteurl/_layouts/thisPage.aspx

But I saw the error page. Please advice, thanks alot

Marc Charmois said...

Hi est,

it is because the name of .aspx file on the file system and the name of the file in the url have to be exactly the same.

So, because you named the file myPage.aspx, you have to access it via
http://siteurl/_layouts/myPage.aspx

All the pages under the layouts directory are called Application Pages.
You will find there all the native pages of SharePoint most of the time written with in line script.
There is the settings.aspx for the site settings.
there is the viewlsts.aspx to see all the content of a site.
there is the viewedit.aspx to modify the view of a list very impressive because you will find if I remember well 4000 rows of in line script within the page.
These pages are very useful because they are executed within the context of a site.
For example assume you have two subsites : subsite1 and subsite2.
if you type this url in the adress bar of a browser
http://mySite/subsite1/_layouts/viewlsts.aspx
the viewlsts.aspx will display all the content of the subsite1.

but if you type

http://mySite/subsite2/_layouts/viewlsts.aspx
the viewlsts.aspx will display all the content of the subsite2.

When you use SharePoint very often you start knowing the file name and sometimes for certain pages, you don't use the menu anymore but type their url in the address bar instead.
It is very good to know how to access certain of these pages by typing the url because sometimes you have not the menu.
Example, the viewlsts.aspx is not available for all the Site Templates.
When your default.aspx page or your master page crashes you are very happy to know the site settings page url to access to your site settings while you cannot display any content for the site :-).

hope this helps.

Marc

est said...

Hi Marc, yes you're right! It should be myPage.aspx instead. I manage to see the page already. Nicely done!

Anyway these are the 4 warnings I see, do you think I can just ignore it or you have better idea on how I can deal with it?


Warning 1
Generation of designer file failed: Inherits attribute not found.

Warning 2
File '~/_layouts/application.master' was not found.

Warning 3
Could not find 'PlaceHolderMain' in the current master page or pages.

Warning 4
Validation (XHTML 1.0 Transitional): Element 'script' is missing required attribute 'type'.

Thanks alot anyway!

Marc Charmois said...

Hi est,

The 3 first warnings are perfectly normal because you are opening in Visual Studio an Application Page wich aimed to excecute within the context of a Sharepoint site.
Visual Studio acts as your page is a page of an usual ASP .Net site, then searches for several elements, the page Directive inherits attribute, the referenced master page, the master page content place holder, and as it does not find them, it warns you.

There is not at the present a version of Visual Studio perfectly adapted for SharePoint.
On the other hand, if you open a page by typing its url in the SharePoint Designer (now free!), the SharePoint Designer will retrieve your sharePoint site master page.
But the SharePoint Designer is used for the Web Design and is not a development tool as powerful as Visual Studio.
Maybe one day SharePoint developers will have a tool that will mix Visual Studio and SharePoint Designer in a single environment.
For the present, we have to use both...

There is still a workaround to display SharePoint pages in Visual Studio so as you can improve this tool for SharePoint and avoid warnings.

see
"Option 4: ASPX pages added to SharePoint Site" section of the following post:
Application Development on MOSS 2007 & WSS V3the last warning is to be taken into account: shame on me, I have forgotten the "type" attribute of the JavaScript <script> tag :-)

Hope this helps.

Marc

est said...

Hi Marc,
Thanks for the reply. May I know what should be added into the "script" tag part then, to prevent the error from showing?

Marc Charmois said...

Hi est,
this is the basic script starting tag with its common attributes adapted to the code of my post

<script type="text/javascript" language="javascript" >

as described in:

The language & type attributes of JavaScriptHope this helps.

Marc

Marc Charmois said...

Hi est,

I have modified the post to take into account all the questions you had reading it (page url, script tag...) to prevent other readers from encountering the same misunderstandings.

Thank you for your contribution.

Marc