Tuesday, August 31, 2010

Security Trimming of Static Links on SharePoint Quick Launch

SharePoint Out Of Box features which comes with team site/Blank site does not provide security trimming of static links on quick launch bar, Whereas this feature is present for all publishing sites. To implement this feature for Team/Blank sites I have decided to write a web part for quick launch bar which reads xml form the custom property of the web part and renders the links. XML contains the required security trimming of static links. Once this web part is ready I would disable the quick launch bar in the master page through SharePoint designer and place this web part there. I am cashing this XML since the operation to convert an XML to dataset is costly.

XML:

<?xml version="1.0" encoding="utf-8"?>

<Home>

<Menu text="Tickets" url="/Lists/Tickets/AllItems.aspx" Roles="Approvers,Enterers,Administrators">

<SubMenu text="New Ticket" url="/_layouts/ProjectName/Ticket List Forms/CaymanTicketsNewForm.aspx" Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="1st Approval" url="/_layouts/ProjectName/Workflow/FirstApproval.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="2nd Approval" url="/_layouts/ProjectName/Workflow/SecondApproval.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="My Tickets" url="/_layouts/ProjectName/Workflow/MyTickets.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="My Action List" url="/_layouts/ ProjectName /Workflow/MyActionList.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

</Menu>

<Menu text="Ticket Status" url="/Lists/Tickets/AllItems.aspx"
Roles="Approvers,Enterers,Receivers,Administrators">

<SubMenu text="Pending" url="/_layouts/ProjectName/Workflow/PendingTickets.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Cancelled" url="/_layouts/ProjectName/Workflow/CancelledTickets.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Completed" url="/_layouts/ProjectName/Workflow/CompletedTickets.aspx"
Roles="Approvers,Enterers,Receivers,Administrators"></SubMenu>

</Menu>

<Menu text="Delete" url="/Lists/Tickets/AllItems.aspx" Roles="Approvers,Administrators">

<SubMenu text="Deletion of Completed Transactions" url="/_layouts/ProjectName/Workflow/DeleteTickets.aspx" Roles="Approvers,Administrators"></SubMenu>

</Menu>

<Menu text="Reports" url="/_layouts/viewlsts.aspx" Roles="Approvers,Enterers,Administrators">

<SubMenu text="Daily Tickets" url="/_layouts/ProjectName/Reports/DailyTickets.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Today's Tickets" url="/_layouts/ProjectName/Reports/TodaysTickets.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Daily By Enterer" url="/_layouts/ProjectName/Reports/DailyTicketsByEnterer.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Monthly By Enterer" url="/_layouts/ProjectName/Reports/MonthlyTicketsByEnterer.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Monthly By Customer" url="/_layouts/ProjectName/Reports/MonthlyTicketsByCustomer.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Outstanding By Customer"
url="/_layouts/ProjectName/Reports/OutstandingByCustomer.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="Today's Transactions"
url="/_layouts/ProjectName/Reports/TodaysTransactions.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

<SubMenu text="End Of Day Download Report"
url="/_layouts/ProjectName/Reports/EndOfDayDownloadReport.aspx"
Roles="Approvers,Enterers,Administrators"></SubMenu>

</Menu>

<Menu text="Administrator" url="/_layouts/viewlsts.aspx" Roles="Administrators">

<SubMenu text="Customers"
url="/Lists/Customers/AllItems.aspx" Roles="Administrators"></SubMenu>

<SubMenu text="Currency" url="/Lists/Currency/AllItems.aspx" Roles="Administrators"></SubMenu>

<SubMenu text="Portfolio" url="/Lists/Portfolio/AllItems.aspx" Roles="Administrators"></SubMenu>

<SubMenu text="Type" url="/Lists/Type/AllItems.aspx" Roles="Administrators"></SubMenu>

</Menu>

</Home>

Web Part Code:

public class SampleQuickLaunch : WebPart

{

#region Variable Decleration
private Utility _oUtility;
private string quickLaunchXML = string.Empty;
private DataSet quickLaunchDS;
private DataView dvSubHeading;

#endregion

#region Constants
const string administratorsSPGroupName = "Administrators";
const string approversSPGroupName = "Approvers";
const string enterersSPGroupName = "Enterers";
const string receiversSPGroupName = "Receivers";

#endregion

[WebBrowsable(true),
WebDisplayName("Quick Launch XML"),
WebDescription("Enter Quick Launch XML"),
Personalizable(PersonalizationScope.Shared)]
public string QuickLaunchXML

{
get { return this.quickLaunchXML; }
set { this.quickLaunchXML = value; }

}
protected override void RenderContents(System.Web.UI.HtmlTextWriter output)

{
this.EnsureChildControls();
base.RenderContents(output);
try

{
string role = string.Empty;
string workflowApprovalSteps = string.Empty;
SPWeb web = SPContext.Current.Web;

createUtilityObject();
string administratorsBranchSPGroupName = web.Title + " " + administratorsSPGroupName;
string enterersBranchSPGroupName = web.Title + " " + enterersSPGroupName;
string approversBranchSPGroupName = web.Title + " " + approversSPGroupName;
string currentUserLoginName = web.CurrentUser.LoginName;
if (_oUtility.IsUserInSharePointGroup(web, administratorsBranchSPGroupName,currentUserLoginName))

{//user will get Admin menu

role = "Administrators";

}
else if (_oUtility.IsUserInSharePointGroup(web, approversBranchSPGroupName, currentUserLoginName))

{//user will get Approver menu

role = "Approvers";

}
else if (_oUtility.IsUserInSharePointGroup(web, enterersBranchSPGroupName, currentUserLoginName))

{//user will get enterer menu

role = "Enterers";

}
else if (_oUtility.IsUserInSharePointGroup(web, receiversSPGroupName, currentUserLoginName))

{//user will get receiver menu

role = "Receivers";

}
if (HttpContext.Current.Cache["Menu"] == null)

{
if (!string.IsNullOrEmpty(this.QuickLaunchXML))

{

quickLaunchDS = ConvertXMLToDataSet(this.QuickLaunchXML);
/*XML for the quick launch menu is getting cached. So this is the same XML which will be used for all the branchs in ProjectName.

* if you want to use a different XML for each branch please prefix the xml with the branch name and then cache it.*/
HttpContext.Current.Cache.Add("Menu", quickLaunchDS, null, DateTime.MaxValue,
TimeSpan.FromMinutes(20), System.Web.Caching.CacheItemPriority.Normal, null);

}

}
else

{

quickLaunchDS = (DataSet)HttpContext.Current.Cache["Menu"];

}
if (role != "Receivers")

{
using (SPWeb myWeb = SPControl.GetContextSite(Context).OpenWeb())

{

workflowApprovalSteps = _oUtility.getNumberOfWorkflowSteps(myWeb);

}

}
if (quickLaunchDS != null && quickLaunchDS.Tables[0].Rows.Count > 0)

{
string strRootURL = web.Url;

output.RenderBeginTag("div class=ms-quicklaunchouter");

output.RenderBeginTag("div class=ms-quicklaunch style=\"width:100%; border: none;\"");

output.RenderBeginTag("table cellpadding=0 cellspacing=0 class=ms-quicklaunch width=100%");

output.RenderBeginTag("tr");

output.RenderBeginTag("td class=ms-quicklaunchheader style=\"border: none;\"");

output.RenderBeginTag("a class=ms-navitem style=\"border-style:none;font-size:1em;font-family:Tahoma;\" href=" + strRootURL + "/_layouts/viewlsts.aspx");

output.Write("View All Site Content");

output.RenderEndTag();//anchor

output.RenderEndTag();//td

output.RenderEndTag();//tr
for (int i = 0; i < quickLaunchDS.Tables[0].Rows.Count; i++)

{
if (quickLaunchDS.Tables[0].Rows[i][3].ToString().Contains(role))

{
//Menu heading

output.RenderBeginTag("tr");

output.RenderBeginTag("td");

output.RenderBeginTag("table class=ms-navheader cellpadding=0 cellspacing=0 border=0 width=100%");

output.RenderBeginTag("tr");

output.RenderBeginTag("td style=width:100%;");
string strTD = "a class=ms-navheader style=\"border-style:none;font-size:1em;font-family:Tahoma;\" href=";

output.RenderBeginTag(strTD + strRootURL + System.Web.HttpUtility.UrlPathEncode(quickLaunchDS.Tables[0].Rows[i][2].ToString()));

output.RenderBeginTag("b");

output.Write(quickLaunchDS.Tables[0].Rows[i][1]);

output.RenderEndTag();//bold

output.RenderEndTag();//anchor tag

output.RenderEndTag();//td

output.RenderEndTag();//tr

output.RenderEndTag();//table

output.RenderEndTag();//td

output.RenderEndTag();//tr
string headingKey = quickLaunchDS.Tables[0].Rows[i][0].ToString();

dvSubHeading = quickLaunchDS.Tables[1].DefaultView;

dvSubHeading.RowFilter = "Menu_Id = '" + headingKey + "'";
//Sub Menu
if (dvSubHeading.Count > 0)

{
for (int x = 0; x < dvSubHeading.Count; x++)

{
if (dvSubHeading[x][2].ToString().Contains(role))

{
if (dvSubHeading[x][0].ToString() == "1st Approval" && workflowApprovalSteps == "1")

{
//do not add 1st approval link

}
else

{

output.RenderBeginTag("tr");

output.RenderBeginTag("td style=\"width:100%;border-style:none;border-width:0px;\"");

output.RenderBeginTag("table class=ms-navitem width=100% border=0 cellpadding=0 cellspacing=0");

output.RenderBeginTag("tr");

output.RenderBeginTag("td style=\"width:100%;border-style:none;border-width:0px;\"");

output.RenderBeginTag("a class=ms-navitem style=\"border-style:none;font-size:1em;\" href=" + strRootURL + System.Web.HttpUtility.UrlPathEncode(dvSubHeading[x][1].ToString()));

output.Write(dvSubHeading[x][0].ToString());

output.RenderEndTag();//anchor

output.RenderEndTag();//td

output.RenderEndTag();//tr

output.RenderEndTag();//table

output.RenderEndTag();//td

output.RenderEndTag();//tr

}

}

}

}

}

}

output.RenderEndTag();//table

output.RenderEndTag();//div

output.RenderEndTag();//div

}

}
catch (System.Exception ex)

{
PortalLog.LogString(ex.StackTrace);
throw new SPException(ex.Message);

}
finally

{

}

}

#region Methods
private DataSet ConvertXMLToDataSet(string xmlData)

{
StringReader stream = null;
XmlTextReader reader = null;
try

{
DataSet xmlDS = new DataSet();

stream = new StringReader(xmlData);
// Load the XmlTextReader from the stream

reader = new XmlTextReader(stream);
//this is costly operation... chk for alternative.

xmlDS.ReadXml(reader);
return xmlDS;

}
catch (System.Exception ex)

{
PortalLog.LogString(ex.StackTrace);
throw new SPException(ex.Message);

}
finally

{
if (reader != null) reader.Close();

}

}// Use this function to get XML string from a dataset
private void createUtilityObject()

{
if (_oUtility == null)

{

_oUtility = new Utility();

}

}

public bool IsUserInSharePointGroup(SPWeb currentWeb, string groupName, string username)

{
bool reachedMax = false;
bool userIsInGroup = false;
// return false if there are invalid inputs
if (String.IsNullOrEmpty(username) String.IsNullOrEmpty(groupName))
return false;
// The Run with elevated privileges needs read permission
// on active directory and the ability to run directory code.
SPSecurity.RunWithElevatedPrivileges(delegate

{
// Get the Site Collection
using (SPSite site = new SPSite(currentWeb.Site.ID))

{
// Get the web
using (SPWeb web = site.OpenWeb())

{
// Find the group
SPGroup group = site.RootWeb.SiteGroups[groupName];
string upperCaseUserName = username.ToUpper();
// Get ad users in the groups. Since MOSS does not support nested groups
// this will always be a collection of AD users and groups
foreach (SPUser user in group.Users)

{
// Check if this is a Group
if (!user.IsDomainGroup)

{
// Verify if the user name matches the user name in group
if (user.LoginName.ToUpper().Equals(upperCaseUserName))

{
// if a match is confirmed, return from the method.
// There is no need to continue

userIsInGroup = true;
break;

}

}
else

{
// If the AD entitiy is a User Group, then check for users in that group
if (IsUserInADGroup(web, user.LoginName, username, out reachedMax))

{

userIsInGroup = true;
break;

}

}

}

}

}

});
return userIsInGroup;

}

#endregion

}