Tuesday, July 29, 2008

Authorization in Web Client Software Factory

This post details the default setup used by WCSF to authorize user requests. The reader is walked through a high-level description of how requests are authorized; additionally, customization points are described so that developers can be aware of where to correctly make modifications if a different authorization process is desired.

This post assumes that the reader has working knowledge of version 2.0 of the Web Client Software Factory.

1. Every request is run through the HandleAuthorization method in the WebClientAuthorizationModule. A delegate is specified that is hooked into the AuthorizeRequest event; this delegate calls the HandleAuthorization method.


public void Init(HttpApplication httpApplication)
{
EventHandler handler = null;
ICompositionContainer rootContainer = httpApplication.Application["__RootContainer__"] as ICompositionContainer;
if (rootContainer != null)
{
if (handler == null)
{
handler = delegate (object sender, EventArgs e) {
IHttpContext context = new HttpContext(httpApplication.Context);
this.HandleAuthorization(rootContainer, context);
};
}
httpApplication.AuthorizeRequest += handler;
}
}



2. The HandleAuthorization() method uses the AuthorizationRulesService to get all authorization rules associated with the request. That list is enumerated, and for each the AuthorizationService is used to check to make sure that the user is able to view the requested URL.

protected virtual void HandleAuthorization(ICompositionContainer rootContainer, IHttpContext context)
{
if (!context.SkipAuthorization)
{
IAuthorizationRulesService service = rootContainer.Services.Get<IAuthorizationRulesService>();
IVirtualPathUtilityService service2 = rootContainer.Services.Get<IVirtualPathUtilityService>();
if (service != null)
{
string[] authorizationRules = service.GetAuthorizationRules(service2.ToAppRelative(context.Request.Path));
if ((authorizationRules != null) && (authorizationRules.Length != 0))
{
IAuthorizationService service3 = rootContainer.Services.Get<IAuthorizationService>(true);
foreach (string str in authorizationRules)
{
if (!service3.IsAuthorized(str))
{
throw new HttpException(0x193, Resources.UserDoesntHaveAccessToTheRequestedResource);
}
}
}
}
}
}
Question: Where does the concrete implementation of the IAuthorizationService get set? Answer: in the ShellModuleInitializer. Developers are directed to set this to EnterpriseLibraryAuthorizationService.

Question: Where do the concrete implementations of the IAuthorizationRulesService and IVirtualPathUtilityService get set?
Answer: in the AddRequiredServices() method of the WebClientApplication class.

protected virtual void AddRequiredServices()
{
AddServiceIfMissing<ModuleConfigurationLocatorService, IModuleConfigurationLocatorService>(this.RootContainer);
AddServiceIfMissing<VirtualPathUtilityService, IVirtualPathUtilityService>(this.RootContainer);
AddServiceIfMissing<AuthorizationRulesService, IAuthorizationRulesService>(this.RootContainer);
AddServiceIfMissing<SessionStateLocatorService, ISessionStateLocatorService>(this.RootContainer);
AddServiceIfMissing<HttpContextLocatorService, IHttpContextLocatorService>(this.RootContainer);
AddServiceIfMissing<ModuleLoaderService, IModuleLoaderService>(this.RootContainer);
AddServiceIfMissing<WebConfigModuleInfoStore, IModuleInfoStore>(this.RootContainer);
AddServiceIfMissing<WebModuleEnumerator, IModuleEnumerator>(this.RootContainer);
AddServiceIfMissing<ModuleContainerLocatorService, IModuleContainerLocatorService>(this.RootContainer);
}
CUSTOMIZATION POINT:
If the developer wishes to change the specific implementations of these or any services defined in the AddRequiredServices() method, the developer may simply override the method and make the specified changes. Note however that this requires that the developer does one of the following:
A. Duplicate all of the code above and makes changes based on the specific implementations they want to use, OR
B. Call the base method and then use the RootContainer.Services.Remove() method to remove the ones to be replaced, and then use the AddServiceIfMissing(ICompositionContainer container) method to add the specific implementations needed.


3. The AuthorizationService calls the Authorize method on the AuthorizationProvider, passing the current principal and a string representing the URL of the request, to determine if the user is authorized.

public bool IsAuthorized(string context)
{
try
{
return this._authorizationProvider.Authorize(Thread.CurrentPrincipal, context);
}
catch (InvalidOperationException)
{
return true;
}
}
Question: How does the concrete implementation of the authorizationProvider get set?
Answer: a factory generates it based on configuration; the default configuration for WCSF applications is set for the AuthorizationRuleProvider.

CUSTOMIZATION POINT:
A different AuthorizationProvider can be specified in configuration. See the snippet from a typical WCSF web.config file. The highlighted section is where a different AuthorizationProvider can be specified. Be sure that the name specified in the “defaultAuthorizationInstance” is the same as the one that is specified under the “name” attribute for the new AuthorizationProvider configuration. While the specific rules do not have to be specified in configuration if another provider is used, all classes inheriting from AuthorizationProvider must provide for a constructor that takes a collection of rules as a parameter. In this case, the rules element can be simply left blank ().

<securityConfiguration defaultAuthorizationInstance="RuleProvider" defaultSecurityCacheInstance="">
<authorizationProviders>
<add type="Microsoft.Practices.EnterpriseLibrary.Security.AuthorizationRuleProvider, Microsoft.Practices.EnterpriseLibrary.Security, Version=3.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" name="RuleProvider">
<rules>
<add expression="R:Approver" name="AllowApprovals"/>
<add expression="R:User" name="AllowAutocomplete"/>
<add expression="R:User" name="AllowCreateOrders"/>
<add expression="R:User" name="AllowBrowseOrders"/>
<add expression="R:User" name="AllowSearchCustomers"/>
</rules>
</add>
</authorizationProviders>
</securityConfiguration>


4. The AuthorizationProvider compares the user and the rule, to see if the user has access.

public override bool Authorize(IPrincipal principal, string ruleName)
{
if (principal == null)
{
throw new ArgumentNullException("principal");
}
if ((ruleName == null) (ruleName.Length == 0))
{
throw new ArgumentNullException("ruleName");
}
base.InstrumentationProvider.FireAuthorizationCheckPerformed(principal.Identity.Name, ruleName);
BooleanExpression parsedExpression = this.GetParsedExpression(ruleName);
if (parsedExpression == null)
{
throw new InvalidOperationException(string.Format(Resources.AuthorizationRuleNotFoundMsg, ruleName));
}
bool flag = parsedExpression.Evaluate(principal);
if (!flag)
{
base.InstrumentationProvider.FireAuthorizationCheckFailed(principal.Identity.Name, ruleName);
}
return flag;
}

For more on how WCSF showcases the ASP.NET security architecture, see this screencast. NOTE: screencast showcases ASP.NET version 2.0 architecture.

Monday, July 28, 2008

Career Advice: Keep a Journal

When I interviewed for my current job, it was incredibly difficult to remember everything I had done before.  We all know how much we can potentially learn during the development process; if a project has been finished for months or years, it can be really hard to remember every obstacle bypassed, every challenge met.

About a year ago, I was walking through a Staples and found one of those grid-ruled college compositions books.  I grabbed it and ever since I've been using it not only to keep notes on projects, but also keep copies (taped onto the pages) of important blog entries and articles I've found.  It has been immensely helpful to remember vital solutions to recurring problems, and proved itself when I had to redo my resume for my latest job search.

Of course, then came Microsoft Office OneNote 2007.  Same concept here, but in a convenient digital form.

If only I could afford that nifty Dell tablet...

Sunday, July 27, 2008

Simple Form Post Example

There's plenty of sites out there that offer services through form POST. Here's a really quick and dirty example of how to submit and receive:
using System;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.IO;
namespace ClassLibrary1
{
public static class FormPostHelper
{
public static string FormPost()
{
string result = string.Empty;

//this sets up the request
Uri postUri = new Uri("http://www.domain.com/pagetopostto.htm");
HttpWebRequest request = (HttpWebRequest)WebRequest.Create(postUri);


//here you define the form fields
string xmlToPass = "FormField=Value";

byte[] bytes = Encoding.UTF8.GetBytes(xmlToPass);
request.Method = "POST";
request.ContentType = "application/x-www-form-urlencoded";
request.ContentLength = xmlToPass.Length;



//open a stream to the target URL
using (Stream requestStream = request.GetRequestStream())
{
requestStream.Write(bytes, 0, bytes.Length);
}



//submit the POST, get the response
HttpWebResponse response = (HttpWebResponse)request.GetResponse();

using (Stream responseStream = response.GetResponseStream())
{
System.IO.StreamReader reader = new StreamReader(responseStream);
result = reader.ReadToEnd();
reader.Close();
}



return result;
}
}
}