ASP.NET Role-Based Security

Role-based security is not a new concept in ASP.NET. What is interesting today is a colleague was asking me how he can authorize access to certain part of an ASP.NET application (that runs using Windows Authentication) based on some attributes set in an Active Directory schema.

Normally, when you use Active Directory with ASP.NET, you would use an Active Directory group as a role, and then use the web.config file to specify authorization for the role to your resources. This works if your Active Directory is designed to have AD groups that map straight to the roles of your applications. But chances are that they don’t map 100%. Or there might be other business reasons that such groups cannot be created.

In order to get around the situation, what we can do is to setup authorization the same way: using web.config and set <Allow roles=”Role1″ /> to authorize access to your resources.

Then we need to override the Application_AuthenticateRequest event of the ASP.NET application. This is implemented via the Global.asax file of the ASP.NET application.

By changing the Application_AuthenticateRequest method, you can insert roles to the principal object the current thread carries.

void Application_AuthenticateRequest(object sender, EventArgs e)
{
  if (Request.IsAuthenticated())
  {
    // some business rules to determine what roles to grant to the current user
    string[] roles = {"Role1", "Role2"};
    Thread.CurrentPrincipal = new GenericPrincipal(Context.User.Identity, roles);  // set the Principal of the current thread
  }
}

So to get back to the example, what my colleague can do is to insert logic inside the if block of the code above and determine by the attribute of the Active Directory record of the current user, and assign the proper roles to the GenericPrincipal object. The rest of the authorization will then be handled by ASP.NET using the Role-based security model.

Happy programming!

April 27, 2007 at 4:41 pm 5 comments

MOSS 2007 WCM Development Part 2 – Customize Variation Root Landing Logic

If you are thinking about using the Variation feature of SharePoint 2007, you will likely have to customize the variation root landing logic of SharePoint. For instance, you give your users web site language preferences and have them redirected to their language of choice after they authenticated with your SharePoint web site.

There are a few ways to customize the variation landing logic as documented by the SDK. Let me illustrate using an example.

Scenario:

  1. SharePoint is configured to use variations for different languages
  2. SharePoint is configured to use Forms Authentication
  3. User Profile is stored in a custom SQL database
  4. User Profile has language preference corresponding to the variations defined for the site
  5. SharePoint will redirect user to the language of choice once they have authenticated

How Variation works:

When SharePoint is setup to use Variations, the root site collection is no longer used to store pages. Instead, you will have sub sites under the web root representing your variations. For example, I have English and French variations, when I browse to my SharePoint site, it will redirect me to either the English or the French sub site. The root welcome page is configured by SharePoint (upon creation of variation labels and site structures) to point to a file called VariationRoot.aspx. This file contains a user control VariationsRootLanding.ascx, which has the code that redirects.

VariationRoot.aspx

The default behavior of the VariationsRootLanding.ascx is to detect the language setting of the client, and try to match it with one of the variation labels. If no matching variation label is found, it will redirect the user to the source variation label. For this example, I set up two variation labels: en-CA and fr-CA (en-CA being my source variation label).

Variation Labels

Notice that the labels are setup to use the culture-region format. If you don’t follow this format, the variation logic will not be able to match the client language properly.

How to customize:

So how about giving users a language preference to your web site regardless of their regional setting? A practical example would be:

  • a user likes to consume the French content
  • He/she logs in to the web site via a public computer that has English as the regional setting
  • We would like to redirect the user to their language preference regardless of what the regional setting the public computer has.

The easiest way is to modify the VariationsRootLanding.ascx file. This user control is actually located in the file system. The path is:

C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\TEMPLATE\CONTROLTEMPLATES\VariationsRootLanding.ascx

This user control does not have any user interface. It is just server side code to redirect a user. The method that we will modify is GetRedirectTargetUrl().

GetRedirectTargetUrl

This code does returns the closest variation label URL based on the client’s regional setting. We still want to keep this code becacuse we need this for anonymous users (we cannot retrieve language preference for users who have not authenticated.) So to modify this, we will add our code around it.

Here is what I considered:

  1. Ensure we are using Forms Authentication
  2. Ensure the user is authenticated
  3. Retrieve the language preference for the currently logged in user
  4. Set our redirect Url by inserting a variation label

Here is a highlight of the modification:

GetRedirectTargetUrlModified

Of course you have to add the required namespaces at the top of the user control. Feel free to modify it and test it out in your own environment.

February 9, 2007 at 1:17 am 15 comments

How to remove recent projects from Visual Studio Start Page

(This is not a new trick but just in case you are looking for it.)

Visual Studio Recent Project List

Sometimes we create projects just for experimentation and then soon after we are done with the experimentation, we delete the project from the file system in order not to clutter up our working environment. But what about Visual Studio? The Start page of Visual Studio has a list of recent projects and it is meant for a convenient feature. But for the experimental projects that no longer exist, that list does not really reflect the reality.

To remove the projects from the list, follow these steps:

  1. Close Visual Studio if it is running.
  2. Start the Registry Editor (run regedit).
    Registry Editor
  3. Navigate to this registry key:
    HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\8.0\ProjectMRUList
    Registry Editor 2
  4. Then delete the key that has the project you do not want to keep in the list.

A little note to keep in mind is that the list only lists consecutive items starting File1. So if in the above list I deleted File6, then only projects corresponding to File1 to File5 will be displayed in the recent project list. File7 and above will not be displayed. If in case you like File7 and above to be displayed, you will need to rename one of the keys so that they form a consecutive numbered list.

kick it on DotNetKicks.com

February 6, 2007 at 12:00 am 80 comments

MOSS 2007 WCM Development Part 1 – Custom Breadcrumb control

In the course of working with Beta 2 and now Beta 2 TR, we ran into a few development challenges. Some are easily fixable, some requires more thoughts. In this series of blog entries, I will attempt to document the challenges we ran into and any solutions we devised on the way.

Variation Feature in MOSS

The variation feature in MOSS is created to address symmetrical site hierarchy structure for multiple languages or multiple devices. For instance, if a site needs to provide content in both English and French languages, the variation feature in MOSS can be used to provide:

  • Variable label creation, this sets up labels to be used in a site structure to logically separate the content for different languages.
  • Variation hierarchy creation, this takes an existing hierarchy that you specify (for example from the “/” of the site) and move it under a source label structure, then replicate the same structure to the target label(s).
  • Workflow for content publishing that works with variation labels.

I am not going to go into the details of the variation feature, you can find out all about them from the MSDN site or download the SDK.


Challenge of the default Site Map Path Control

The challenge we ran into after setting up variation labels is that the variation labels are always displayed on the default out-of-the-box site map path control (aka breadcrumb). For example, if the source variation label is “English” and the target variation label is “French”, when a user navigates to the root of the site, the user will automatically be redirected to the proper variation label site. This is done via the Variation Root logic which will detect the locale via the browser, and if the locale matches one of the variation labels, the user will be redirected to that label. And if no variation labels match the locale, then the user will be redirected to the source label. All this is fine except the variation is a site level being created under root, the page that the user is redirected to is no longer at the root of the site. That is, if a user is redirected to the “Français” site, the hierarchy being displayed on the site map will be:

french01.jpg

This is not a desirable condition because:

  • The user wanted to go to the home page of the site, but instead the user is redirected to a sub site under root.
  • The extra variation site structure does not add value to the browsing experience
  • The default.aspx page at the root level can not be navigated to because of the variation root redirect behaviour, and displaying the Home link on the site map which redirects the users back to the variation label level (e.g. Français) can be confusing.

The desired functionality of the site map is simply to skip the rendering of the variation labels so that the net effect of a user navigating to the root will just be silently redirected to the variation label site, but not giving the user a visual clue the actual hierarchy level they are at. That is, if a user is to be redirected to the Français variation site, the site map path should still only render:

french02.jpg

as the current site level to the user because that is the context the user is expecting.


The Solution

To do this I created a custom site map path web control that:

  1. Can trim the variation labels (or any node name for that matters) using the “IgnoreNodeName” attribute.
  2. Can specify the separator string using “Separator” attribute. For example: ” >> “.
  3. Can specify the root node image url using “RootNodeImageUrl” attribute so that we don’t have to render the root collection name.
  4. Can specify not to render the current location as a link using “RenderCurrentNodeAsLink” attribute. When set to false, the current node will be rendered as a label.
  5. Can specify a CSS Style for the current node item using “CurrentNodeStyleCssClass” attribute.

Some of the features are already in the ASP.NET Site Map Path control. The key new features are the first 3 features. The result is a site map path control that allows me to specify which node to skip rendering, as well as giving enough control over how the site map path is rendered.


Development Method

Since MOSS is built on top of ASP.NET 2.0, a web custom control written for an ASP.NET 2.0 web site will also work inside MOSS. To ease development, I first set up an ASP.NET web site that mimics the MOSS 2007 site structure and file structure. Then, I created the web custom control and tested and debugged it inside the ASP.NET web site before deploying it to MOSS. The pieces include:

  • Create a new web site using VS2005.
  • Set up a master page to be used with the content pages, MOSS 2007 makes extensive use of master pages.
  • Set up some sample ASPX pages that use the master page file.
  • Set up a web.sitemap file as the Site Map data source, referencing the sample ASPX pages just created.
  • Create the custom site map path control in a separate project.
  • Register the custom site map path DLL with the master page, and place the control on the master page.
  • Assign values to the different attributes to test the behaviours. Debug then test again.

Once the custom control is working in an ordinary ASP.NET web site, it is time to test it in the MOSS environment. Here are the steps I use:

  1. Copy the DLL of the custom control to the GAC of the MOSS server.
  2. Edit the web.config file of the SharePoint application and add the custom control to the SafeControlList.
  3. Use SharePoint Designer, open a Master Page and save it using a different name to avoid messing up the out-of-the-box master page.
  4. Add a Page directive to register the assembly to the master page and define a tag name.
  5. Add the control to the page by using the defined tag name:control name syntax.
  6. Save the master page and check it in as a major version, and approve the changes to make it public.
  7. Navigate to a site in your MOSS application. Click on Site Action and select Settings | Modify all settings.
    modifyallsitesettings.jpg
  8. Click Master page link under the Look and Feel Group
    masterpage.jpg
  9. Select the newly saved master page, and select the Reset all subsites to inherit this Site Master Page setting checkbox.
    selectmasterpage1.jpg
  10. Click OK to apply the master page.

If all steps are performed successfully, you will see your site having the newly added custom control on the page.

The Result

To illustrate the point, I edited the master page so that the new custom site map path and the original site map path are displayed one on top of the other. As you can see, the top custom site map path is a lot more concise and does not display the unnecessary variation label.

compare.jpg

Updates:
You can view the source code of the custom site map path control:


using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using System.Text.RegularExpressions;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace KamLau.WebControls
{
  [DefaultProperty("RenderCurrentNode")]
  [ToolboxData("")]
  public class CustomBreadcrumb : WebControl
  {
    # region Properties

    /// 
    /// This attribute is created to accomodate using an image (like a house for example) to represent the root node.
    /// If no value is specified, the textual representation of the root node is used.  Otherwise the image is displayed.
    /// 
    private const string defaultRootNodeImageUrl = "";
    private string rootNodeImageUrl = defaultRootNodeImageUrl;
    [Category("Appearance")]
    [DefaultValue(defaultRootNodeImageUrl)]
    [Description("A url of an image to be displayed instead of a text label/hyperlink for the root node.")]
    [Browsable(true)]
    public string RootNodeImageUrl
    {
      get { return rootNodeImageUrl; }
      set { rootNodeImageUrl = value; }
    }

    /// 
    /// The string that will be used as the separator of the breadcrumb nodes.  If no value is specified, ">" will be used.
    /// 
    private const string defaultSeparator = ">";
    private string separator = defaultSeparator;
    [Category("Appearance")]
    [DefaultValue(defaultSeparator)]
    [Description("A string used as separators between breadcrumb nodes.")]
    [Browsable(true)]
    public string Separator
    {
      get { return separator; }
      set { separator = value; }
    }

    /// 
    /// A boolean to indicate if the current node should be rendered as a hyperlink.  Default value for this property is false.
    /// 
    private const bool defaultRenderCurrentNodeAsLink = false;
    private bool renderCurrentNodeAsLink = defaultRenderCurrentNodeAsLink;
    [Category("Appearance")]
    [DefaultValue(defaultRenderCurrentNodeAsLink)]
    [Description("Indicates if the current node should be rendered as hyperlink.")]
    [Browsable(true)]
    public bool RenderCurrentNodeAsLink
    {
      get { return renderCurrentNodeAsLink; }
      set { renderCurrentNodeAsLink = value; }
    }


    private const string defaultCurrentNodeStyleCssClass = "";
    private string currentNodeStyleCssClass = defaultCurrentNodeStyleCssClass;
    [Category("Appearance")]
    [DefaultValue(defaultCurrentNodeStyleCssClass)]
    [Description("The CSS style to use with the current node.")]
    [Browsable(true)]
    public string CurrentNodeStyleCssClass
    {
      get { return currentNodeStyleCssClass; }
      set { currentNodeStyleCssClass = value; }
    }
	
    
    /// 
    /// A string with comma delimited values which will be ignored when the breadcrumb is rendered.  This is especially useful
    /// for trimming the variation labels in MOSS2007, or if any level of the hierarchy in the navigation needs to be skipped.
    /// 
    private const string defaultIgnoreNodeName = "";
    private string ignoreNodeName = defaultIgnoreNodeName;
    [Category("Appearance")]
    [DefaultValue(defaultIgnoreNodeName)]
    [Description("The substrings (name of navigation nodes) in this comma delimited string will not be rendered on the breadcrumb.")]
    public string IgnoreNodeName
    {
      get { return ignoreNodeName; }
      set { ignoreNodeName = value; }
    }


    private const string defaultSiteMapProvider = "AspnetXmlSiteMapProvider";
    private string siteMapProvider = defaultSiteMapProvider;
    [Category("Appearance")]
    [DefaultValue(defaultSiteMapProvider)]
    [Description("Specify the name of the current site map provider")]
    public string SiteMapProvider
    {
      get { return siteMapProvider; }
      set { siteMapProvider = value; }
    }
	
    
    #endregion 
    
    
    Stack LinkStack = new Stack();
    
    /// 
    /// Create HyperLink objects and pop to the stack, recursively going up the hierarchy
    /// 
    /// 
    /// 
    private void TraverseUp(SiteMapNode currentNode, Stack linkStack)
    {
      if (currentNode != null)
      {
        HyperLink currentLink = new HyperLink();
        currentLink.Text = currentNode.Title;
        currentLink.NavigateUrl = currentNode.Url;
        currentLink.Attributes.Add("alt", currentNode.Description);
        linkStack.Push(currentLink);
        if (currentNode.ParentNode != null)
        {
          TraverseUp(currentNode.ParentNode, linkStack);
        }
      }
    }
    
    protected override void RenderContents(HtmlTextWriter output)
    {
      TraverseUp(SiteMap.CurrentNode, LinkStack);
      bool isRoot = true;
      while(LinkStack.Count > 0)
      {
        Label spanTag = new Label(); // use label to wrap  tags around nodes.
        HyperLink nodeLink = (HyperLink) LinkStack.Pop(); // use a stack (FILO) to reverse the order of traversing up the hierarchy

        if (!IsIgnore(nodeLink.Text))
        {
          if (!isRoot)
          {
            Label separatorLabel = new Label();
            separatorLabel.Text = Separator;
            separatorLabel.RenderControl(output);
          }
          if (LinkStack.Count == 0 && !RenderCurrentNodeAsLink)
          {
            if (RootNodeImageUrl != String.Empty && isRoot)
            {
              Image rootNodeImage = new Image();
              rootNodeImage.ImageUrl = RootNodeImageUrl;
              rootNodeImage.RenderControl(output);
            }
            else
            {
              spanTag.Text = nodeLink.Text;
              spanTag.CssClass = CurrentNodeStyleCssClass;
              spanTag.RenderControl(output);
            }
          }
          else
          {
            if (RootNodeImageUrl != String.Empty && isRoot)
            {
              nodeLink.ImageUrl = RootNodeImageUrl;
            }
            spanTag.Controls.Add(nodeLink);
            spanTag.RenderControl(output);
          }
        
          isRoot = false;
        }
      }
    }
    
    /// 
    /// Determine if the node should be ignored.
    /// 
    /// 
    /// 
    private bool IsIgnore(string nodeName)
    {
      bool isIgnore = false;
      string[] ignoreNodeNames;
      if(IgnoreNodeName != String.Empty)
      {
        char[] splitChar = new char[1];
        splitChar[0] = ',';
        ignoreNodeNames = IgnoreNodeName.Split(splitChar);

        foreach (string ignoreString in ignoreNodeNames)
        {
          if (Regex.IsMatch(nodeName, ignoreString, RegexOptions.IgnoreCase))
          {
            isIgnore = true;
            break;
          }
        }
      }
      return isIgnore;
    }
  }
}

September 28, 2006 at 11:45 am 30 comments


October 2014
M T W T F S S
« Apr    
 12345
6789101112
13141516171819
20212223242526
2728293031  

Feeds

Blog Stats

  • 112,116 hits

Follow

Get every new post delivered to your Inbox.