I got the question from a customer who wanted to have a view of every user on a site with all the permissions that the user had per list (since you can set permissions per list). Sounds pretty forward huh? :)
One challenge I had to face lies in the fact that this customer is a big fan of creating custom sitegroups and cross-site groups.
Another (personal) challenge was that I wanted to publish this information in a custom list on the site where the webpart is dropped upon. You may wonder why I want to publish the information back in Sharepoint again. Well there are 3 reasons :
1. Export to Excel (requirement of the customer)
2. Sorting/Filtertering/Group By of columns
3. Intergration with AD (user presence)
Thus, the first thing was to create a function to retrieve all the permissions per list for each user on a site.
private void GetSecurityForWeb(string url)
{
Stopwatch sp = new Stopwatch();
sp.Start();
ArrayList AnalyzeItems = new ArrayList();
SPSite site = new SPSite(url);
SPWeb web = site.OpenWeb();
try
{
Label1.Text += "<BR>Iterating through "+web.Title.ToString();
SPUserCollection users = web.Users;
foreach (SPUser user in users)
{
foreach (SPList list in web.Lists)
{
System.Collections.Specialized.StringCollection permissions = new System.Collections.Specialized.StringCollection();
if (!list.Permissions.Inherited)
{
SPPermissionCollection perms = list.Permissions;
foreach (SPPermission perm in perms)
{
if (user.ID == perm.Member.ID)
{
foreach (SPRole role in user.Roles)
{
if (role.Type != SPRoleType.None)
{
if (!permissions.Contains(role.Type.ToString()))
{permissions.Add(role.Type.ToString());}
}
}
}
else
{
foreach (SPRole role in user.Roles)
{
if (role.ID == perm.Member.ID)
{
if (role.Type != SPRoleType.None)
{
if (!permissions.Contains(role.Type.ToString()))
{permissions.Add(role.Type.ToString());}
}
else
{
if (!permissions.Contains(role.Name.ToString()))
{permissions.Add(role.Name.ToString());}
}
}
foreach (SPGroup group in role.Groups)
{
if (group.ID == perm.Member.ID)
{
if (!permissions.Contains(perm.PermissionMask.ToString()))
{permissions.Add(perm.PermissionMask.ToString());}
}
}
}
foreach (SPGroup group in user.Groups)
{
if (group.ID == perm.Member.ID)
{
if (!permissions.Contains(perm.PermissionMask.ToString()))
{permissions.Add(perm.PermissionMask.ToString());}
}
}
}
}
}
else
{
foreach (SPRole role in user.Roles)
{
if (role.Type != SPRoleType.None)
{
if (!permissions.Contains(role.Type.ToString()))
{permissions.Add(role.Type.ToString());}
}
else
{
if (!permissions.Contains(role.Name.ToString()))
{permissions.Add(role.Name.ToString());}
}
foreach (SPGroup group in role.Groups)
{
if (!permissions.Contains(group.Name.ToString()))
{permissions.Add(group.Name.ToString());}
}
}
}
//Populating of all the groups where the user is a member of
string groups = string.Empty;
int teller = 1;
foreach (SPGroup group in user.Groups)
{
if (user.Groups.Count == teller)
{groups += group.Name.ToString();}
else
{groups += group.Name.ToString() + " " ;}
teller++;
}
//Adding all the information in an object and pushing this object in an ArrayList
AnalyzeItem item = new AnalyzeItem();
item.Site = web.Title;
item.Username = user.Name;
item.UserID = user.ID.ToString();
item.List = list.Title;
string _perm = string.Empty;
foreach (string perm in permissions)
{
_perm += " " + perm;
}
item.Permissions = _perm;
item.Group = groups;
AnalyzeItems.Add(item);
}
web.Close();
web.Dispose();
}
}
catch(Exception error)
{
Label1.Text += error.Message.ToString();
}
finally
{
site.Close();
site.Dispose();
}
sp.Stop();
Fetch = sp;
AddListItem(url, AnalyzeItems);
}
So what I do is gather all the permissions and store them in a custom class called AnalyzeItem which looks like this :
So now we have all the data stored in the ArrayList, now we want to store this information into a List
private void AddListItem(string url, ArrayList items)
{
Label1.Text += "<BR>Pushing items into list";
Stopwatch sp = new Stopwatch();
sp.Start();
SPSite _Site = new SPSite(url);
SPWeb _Web = _Site.RootWeb;
try
{
Label1.Text += "<BR>Checking if list exists";
SPList analyse = _Web.Lists["Analyse"];
}
catch(Exception error)
{
Label1.Text += "<BR>List not found, therefore creating it";
CreateList(url);
Label1.Text += "<BR>List created";
}
finally
{
_Web.Close();
_Web.Dispose();
_Site.Close();
_Site.Dispose();
}
SPSite site = new SPSite(url);
SPWeb web = site.RootWeb;
SPWeb _compareWeb = site.OpenWeb();
try
{
Label1.Text += "<BR>Emptying list if items already exist";
SPList analyse = web.Lists["Analyse"];
if (analyse.Items.Count > 0)
{
for (int i=analyse.Items.Count-1; i>-1; i--)
{
if (analyse.Items[i]["Site"].ToString() == _compareWeb.Title.ToString())
{
analyse.Items.Delete(i);
}
}
}
Label1.Text += "<BR>List emptied!";
foreach (AnalyzeItem _item in items)
{
SPListItem item = analyse.Items.Add();
item["Title"] = _item.Username;
item["UserName"] = _item.UserID;
item["List"] = _item.List;
item["Permissions"] = _item.Permissions;
item["Groups"] =_item.Group;
item["Site"] = _item.Site;
item.Update();
}
}
catch (Exception error)
{
Label1.Text += "<BR>"+error.Message.ToString();
}
finally
{
web.Close();
web.Dispose();
site.Close();
site.Dispose();
}
sp.Stop();
Import = sp;
}
Now you may wonder what the function CreateList, as you may have guessed by the name it creates a custom list based on a customlist template. See the following code chunk on how I did this. I had some difficulties since the customer has a Portal + teamsites environment so every site beneath the portal is a sitecollection instead of a site. Because of this I could not use the custom list template since this is per site-collection and cannot be referenced over sitecollections. So I choose to physically copy the template from a source site (where the template can ben maintained as well) to the site where the webpart was dropped upon.
private void CreateList(string url)
{
//First copy the list template from the source site to the current site
SPSite siteCollection = new SPSite(sourcesite);
SPWeb web = siteCollection.OpenWeb();
SPWeb destweb = SPControl.GetContextWeb(Context);
try
{
SPFolder srcFolder = web.Folders["_catalogs"].SubFolders["lt"];
SPFile srcFile;
if (destweb.WebTemplate == "MPS")
{
srcFile = srcFolder.Files["Analyse.stp"];
}
else
{
srcFile = srcFolder.Files["AnalyseNon.stp"];
}
Label1.Text += "<BR>Template found! " + srcFile.Name.ToString();
try
{
Label1.Text += "<BR>Opening destination web";
SPFolder destFolder = destweb.Folders["_catalogs"].SubFolders["lt"];
SPFileCollection destFiles = destFolder.Files;
Label1.Text += "<BR>Filecollection opened";
string destURL = destFolder.Url + "/" + srcFile.Name;
byte[] binFile = srcFile.OpenBinary();
destFiles.Add(destURL, binFile);
Label1.Text += "<BR>Template saved to "+destURL.ToString();
}
catch(Exception _error)
{
Label1.Text+="<BR>Problem adding template" + _error.Message.ToString();
}
}
catch(Exception error)
{
Label1.Text += "<BR>List creation failed due to : " + error.Message.ToString();
}
finally
{
web.Close();
web.Dispose();
siteCollection.Close();
siteCollection.Dispose();
}
try
{
Label1.Text += "<BR>Trying to create list based on new template";
SPSite destSite = SPControl.GetContextSite(Context);
SPWeb destWeb = destSite.OpenWeb();
Label1.Text += "<BR>Opened the current web";
SPListTemplateCollection customListTemplates = destSite.GetCustomListTemplates(destWeb);
Label1.Text += "<br>#customtemplates" + customListTemplates.Count.ToString();
foreach(SPListTemplate temp in customListTemplates)
{
Label1.Text += "<BR>" + temp.Name.ToString();
}
if (destWeb.WebTemplate == "MPS")
{
SPListTemplate listTemplate = customListTemplates["Analyse"];
destWeb.Lists.Add("Analyse", "", listTemplate);
}
else
{
SPListTemplate listTemplate = customListTemplates["AnalyseNon"];
destWeb.Lists.Add("Analyse", "", listTemplate);
}
}
catch(Exception ___error)
{
Label1.Text += "<BR>" + ___error.Message.ToString();
}
}
As you may have noticed, I use different custom list templates when a different webtemplate is applied to the site. This has to do with site definitions are stored into a custom list template (see KB: Custom List Templates Do Not Appear on the "Create Page" Page of a Site) took me a couple of hours to figure this thing out btw. Since the file is physically there the frustation grew and grew when the object model said there was no custom list template to be found in the gallery :)
And finally we need to build the user interface where the user can select a site and press a button to populate the list. I embedded some javascript to show something an animated gif while the webpart is loading his stuff, so the user doesn't think that the webpart is not functioning. Also I built in a check that only webadmin's can run this tool, since some adminstrative privileges are required when userproperties are queried.
public class CrossSiteGroupWebPart : Microsoft.SharePoint.WebPartPages.WebPart
{
public Stopwatch Fetch;
public Stopwatch Import;
public StringBuilder sb = new StringBuilder();
LinkButton refreshButton;
private Label Label1;
public string nummer = "";
DropDownList drpSiteList;
private const string defaultText = "";
private string text = defaultText;
[Browsable(true),
Category("Miscellaneous"),
DefaultValue(defaultText),
WebPartStorage(Storage.Personal),
FriendlyName("Text"),
Description("Text Property")]
public string Text
{
get
{
return text;
}
set
{
text = value;
}
}
/// <summary>
/// This method gets the custom tool parts for this Web Part by overriding the
/// GetToolParts method of the WebPart base class. You must implement
/// custom tool parts in a separate class that derives from
/// Microsoft.SharePoint.WebPartPages.ToolPart.
/// </summary>
///<returns>An array of references to ToolPart objects.</returns>
// public override ToolPart[] GetToolParts()
// {
// ToolPart[] toolparts = new ToolPart[2];
// WebPartToolPart wptp = new WebPartToolPart();
// CustomPropertyToolPart custom = new CustomPropertyToolPart();
// toolparts[0] = wptp;
// toolparts[1] = custom;
// return toolparts;
// }
/// <summary>
/// Render this Web Part to the output parameter specified.
/// </summary>
/// <param name="output"> The HTML writer to write out to </param>
protected override void CreateChildControls()
{
nummer = this.ID.ToString();
Label1 = new Label();
this.Controls.Add(Label1);
refreshButton = new LinkButton();
refreshButton.Text="<img src='/_layouts/images/REFRESH.GIF' border='0' />Refresh overview";
refreshButton.Attributes.Add("onclick","this.style.display='none';"+
"var obj_msg = document.getElementById('waitmessage"+nummer+"'); "+
"if (obj_msg != null) obj_msg.style.display='';"+
"var obj_tasks = document.getElementById('tasks"+nummer+"'); "+
"if (obj_tasks != null) obj_tasks.style.display='none';" +
"javascript:setTimeout('UpdateImg"+nummer+"()');");
refreshButton.Click+=new EventHandler(refreshButton_click);
this.Controls.Add(refreshButton);
drpSiteList = new DropDownList();
try
{
drpSiteList.DataSource = GetSubWebs();
drpSiteList.DataTextField = "Title";
drpSiteList.DataValueField = "Url";
drpSiteList.DataBind();
}
catch(Exception error)
{
Label1.Text += "<BR>" + error.Message.ToString();
}
this.Controls.Add(drpSiteList);
base.CreateChildControls ();
}
private void refreshButton_click(object o, System.EventArgs e)
{
try
{
GetSecurityForWeb(drpSiteList.SelectedValue.ToString());
UpdateStopWatch(drpSiteList.SelectedValue.ToString());
Label1.Text = "<BR>Refreshing is done";
Label1.Text +="<BR>Fetching items lasted : " +Fetch.Elapsed.Minutes+ "m " + Fetch.Elapsed.Seconds + "s" + " Importing items lasted : " +Import.Elapsed.Minutes+ "m " + Import.Elapsed.Seconds + "s";
Label1.Text +="<BR><a href='Lists/Analyse/AllItems.aspx'>Click here</a> to view the grid";
}
catch(Exception error)
{
Label1.Text += "<BR>" + error.Message.ToString();
}
}
protected override void RenderWebPart(HtmlTextWriter output)
{
SPWeb web = SPControl.GetContextWeb(Context);
if (web.UserIsWebAdmin)
{
try
{
output.Write("<script language=javascript>");
output.Write("function UpdateImg"+nummer+"() {");
output.Write("var img = document.getElementById('taskpic"+nummer+"');");
output.Write("if (img != null) img.src = '_layouts/images/bigrotation2.gif';");
output.Write("return false;");
output.Write("}");
output.Write("</script>");
output.Write("<div id='waitmessage"+nummer+"' style='Z-INDEX:10; DISPLAY:none; WIDTH:100%; POSITION:relative; HEIGHT:100%'>");
output.Write("<table border='0' style='BORDER-COLLAPSE: collapse' width='100%' height='100%' cellspacing='1' bgcolor='#ffffff' bordercolor='#000000'>");
output.Write("<tr>");
output.Write("<td align='middle' bordercolor='#ffffff'>");
output.Write("<img id='taskpic"+nummer+"' src='_layouts/images/bigrotation2.gif'>");
output.Write("</td>");
output.Write(" </tr>");
output.Write(" </table>");
output.Write("</div>");
output.Write("Please select a site to display information from : ");
drpSiteList.RenderControl(output);
refreshButton.RenderControl(output);
Label1.RenderControl(output);
}
catch(Exception error)
{
output.Write(error.Message.ToString());
}
}
else
{
Label1.Text = "You don't have enough permissions to run this webpart";
}
}