Thomas Gathings II
hey buddy, the truth hurts.

Configurable Authorization: Subclassing ASP.Net MVC AuthorizeAttribute

 With ASP.Net MVC, you can easily use AuthorizeAttribute to control access to controllers and actions. I found it limiting within the context of Windows Authentication. First, I wanted to configure the roles outside of an attibute. Properties of AuthorizeAttribute, as with all attributes, must be set a design-time, such as [Authorize(Roles = “MyCompany\AppAdmin”)]. I want to break that out to configuration so I can have [Authorize(Roles = “Editor”)] and configure the Editor role like this EditorRole=”MyCompany\AppAdmin”, where MyCompany\AppAdmin is an Active Directory security group.

On a semi-related note, it isn't possible to use Users and Roles together with the AuthorizeAttribute. The implementation of AuthorizeCore (an internal, form template pattern in the AuthorizeAttribute class) is written to assert both users and roles. Crack it open with reflector and it's clearly evident. In the following example, only Joe.User can access this controller. Everyone else, including members of the Admins role, will recieve a 401.

[Authorize(Roles="Admins", Users="MyCompany\\Joe.User")]

The following code achieves my goals: configurable authorization using Roles, Users, or both in context of Windows Authentication.

 

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]

public class AuthorizeByConfigurationAttribute : AuthorizeAttribute

{

    private static string[] SafeSplit(string toSplit)

    {

        if (string.IsNullOrEmpty(toSplit)) return new string[0];

        var splitString = toSplit.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

        return splitString;

 

    }

 

 

    protected override bool AuthorizeCore(HttpContextBase httpContext)

    {

        if (false == string.IsNullOrEmpty(Users))

        {

            throw new NotSupportedException("Use of the Users property is not allowed; match Roles to settings in configuration with convention 'GroupsInRoleOf.<NamedRole>' for groups or 'UsersInRoleOf.<NamedRole>', where <NamedRole> is anything, such as Admin.");

 

        }

 

        string rolesToCheck = ConfigurationManager.AppSettings["GroupsInRoleOf." + Roles];

        string usersToCheck = ConfigurationManager.AppSettings["UsersInRoleOf." + Roles];

 

        if (httpContext == null)

        {

            throw new ArgumentNullException("httpContext");

        }

 

        IPrincipal user = httpContext.User;

 

        if (!user.Identity.IsAuthenticated)

        {

            return false;

        }

 

        if ((SafeSplit(usersToCheck).Length > 0) && SafeSplit(usersToCheck).Contains<string>(user.Identity.Name, StringComparer.OrdinalIgnoreCase))

        {

            return true;

        }

 

        if ((SafeSplit(rolesToCheck).Length > 0) && SafeSplit(rolesToCheck).Any<string>(new Func<string, bool>(user.IsInRole)))

        {

            return true;

        }

 

        return false;

    }

 

 

}

 

 

This first-cut uses concatenated keys in app settings. Better to have a custom ConfigurationSection, a simple class configured with an IOC-container, or the like.

 

Sample appSettings

 

 

<add key="GroupsInRoleOf.Editor" value="MyCompany\MyAppEditors"/>

<add key="UsersInRoleOf.Editor" value="MyCompany\JoeThePresident"/>

Sample attribute

 

[AuthorizeByConfiguration(Roles = "Editor")]

 

Linq to SQL: an optimization on working with object graphs

I was working on a small web application that was built very quickly using Linq to SQL and I wanted to ensure that a particular page wasn't loading more data than needed into memory. This particular page had a simple GridView, and in the one and only item template, there was an ASP.Net Image (along with some other stuff).

The data source was of type IQueryable<Article> and an Article may have zero to many ArticleImages. A relationship was present in the dbml, so the ArticleImages were generated as an entity set property. The requirements were to show a thumbnail if there was an image (the first) or else nothing.

On the image, I set ImageUrl to

<%# DataBinder.Eval(Container.DataItem,"ArticleId","~/GetArticleImage.ashx?thumbnail=y&articleid={0}" ) %>

and Visible to the expresssion:

<%# ((Article)Container.DataItem).ArticleImages.Count %>

 The page rendered properly but when I checked the data context log, I noticed that for every row in the GridView, the entire ArticleImage row was being loaded. As it turns out, this is well-documented behavior; in an object graph, if any property of an entity set is accessed, the entire entity is loaded. It is lazy-loaded by default and while this can be changed with DataLoadOptions, I don't believe you can specify options for individual properties.

What I needed at this point was an efficient way to test whether or not there were any images. There are other ways to do this, such as:

  • add a calculated column leveraging a scalar UDF on your table
  • work with a view exposing only narrow metadata
  • separate your metadata into an intermediary table

 I went with the addition of a property on the Article entity that counts a projected int and it looks like this:

public bool HasImage

{

    get

    {

        GreenCreditDataContext db = DataContextSessionManager<GreenCreditDataContext>.Instance;

        return db.ArticleImages.Where(x => x.ArticleId == ArticleId).Count() > 0;

 

    }

}

Ok so that problem was solved. However, there was another one. In the handler code that streams the image data to the browser, I was also loading the entire ArticleImage row. This was not necessary as I only needed the title and either the thumbnail data or the image data. Originally, I was calling the FirstOrDefault extension method on IQueryable<ArticleImage> from the data context. After a null check, I accessed either the ThumbnailData or the ImageData property, depending on the existence of a simple thumbnail querystring. However, the log shows that the entire row was loaded. My solution to this was to exercise Where (with the same Func expression used earlier), then chain that to a projection.

if(thumbnail)
{
    var s = articleImages
        .Select(x => new { Data = x.ThumbnailData, Title = x.Title })
        .FirstOrDefault();
 
    if (null != s) Write(s.Data, s.Title);
}
else
{
    var s = articleImages
        .Select(x => new { Data = x.ImageData, Title = x.Title})
        .FirstOrDefault();
 
    if (null != s) Write(s.Data, s.Title);
}
To wrap up, especially when working with datatypes of unrestrained size (such as varbinary(max)), check the logs to ensure your optimizing as necessary.

Reverse Joins with Transient Provisioning

If you found this, then you know I'm talking about Microsoft Identity Integration Server (MIIS) / Identity Lifecycle Manager (ILM).  A reverse join is a concept that attempts to solve problems arising from joining existing connector space objects to newly created metaverse objects. Microsoft has a great white paper on the subject.

I discovered that I might care about this subject because I couldn't get a few unit tests to pass. I had unit tests covering two inactivation scenarios. The tests followed two accounts (User A, User B) over 3 synchronization cycles. The first cycle provisioned the accounts. The second cycle inactivated them. The third cycle 'brought them back to life'. My tests correspond with what you'd expect: Assert the AD account is there and active, assert the AD account is disabled, assert the AD account is active. User A was inactivated with a status change and was correctly covered by deprovisioning actions with passing tests. User B, however, gave me a fit. User B was deleted (effectively deleted, I simply filtered out her account before the connector space import) before cycle 2 as part of the test fixture setup, deferring to object deletion rules. Her AD account was correctly disabled after cycle 2. So far, so good. However, when I brought her 'back to life' by removing the filter, I received provisioning errors about existing duplicate objects, exactly as outlined in the microsoft whitepaper.

The whitepaper, although lacking in implementation details, does a great job contrasting 2 practical approaches to this problem using reverse joins. One, using transient provisioning is simpler, but it smacks you with a sinking stomach feeling because you are PURPOSEFULLY CREATING DUPLICATE CONNECTOR SPACE OBJECTS!  The other, reverse joins with auxillary management agents, has been posted about by Craig Martin. I was at first more interested in this approach because it sounds elegant but also very complex. I despise complexity so much that I gave the other approach some more thought. Glad I did, because it turns out to be valid, robust, and as easy as this:

1. Run profiles must be run in an exact order, where at least a delta synchronization is performed on the CS before export

2. Add this code to your provisioning block, testing the condition of more than one connectors:

ElseIf numConnectors > 1 Then

 Dim connector As CSEntry

 For Each connector In activeDirectoryManagementAgent.Connectors
  If connector.ConnectionRule = RuleType.Provisioning Then connector.Deprovision()

 Next

End If

 

If you're interested, read my blog entry about unit testing identity management solutions.

Cheers!

Thomas

Gain confidence with your MIIS or ILM Identity Management solutions

Build automated unit tests for your identity management solutions just like you would a normal development  project. Trust me. Here's why:

Test Driven Design: design your test first. do your craft until the test passes.
Comfort and assurance: you know because you've run them 1001 times. others can run them too.
Free documentation: requirement and business rules can be inferred from the test cases.
Fix problems instead of looking for them: you will spend almost no time looking for defects

Yes, it requires an upfront investment but the payback is tremendous. Here's how.

First, familiarize yourself with a unit testing framework. My favorite is NUnit, which is simple, free, and used by the masses.

Next, get creative. If your connected data sources consist of SQL Server, Active Directory, or any open platform, you'll simply be setting up data to validate your tests. For example, using SQL Server, I like to create a table with as little representative data as I can get away with, maybe 5 - 20 rows. I call this table UnitTestExportStatic. Then, I clone that table into another table, UnitTestExportRunnable. During setup of my test execution, I truncate UnitTestExportRunnable and copy all the rows from UnitTestExportStatic. I can freely make changes, such as updating an attribute to test the flow, to UnitTestExportRunnable throughout the testing of the management agent run profile sequences.

Create one test fixture (a group of related tests) per management agent run profile sequence. Run as many sequences as you need to test some scenarios. For instance, my favorite tests have an active person that goes inactive, then gets reactivated. This takes 3 cycles to test each condition.  Name them something like A__InitialRun, B__SecondRun, etc, and be sure they run in order. Test fixtures support running setup code, which will run before any tests. Here is where you do things like delete accounts, update phone numbers, create accounts, and invoke the run profiles using the remote wmi calls.

[TestFixtureSetUp]
public void SetUpAll()
{

 new Setups.SetupEntitleSubContractorsForTeamSiteAndSubportalAccess().Run();
 new Setups.SetupAddMockUser02().Run(); 
 new Setups.SetupInactivateMockUser06().Run();
 new Setups.SetupUpdateMockUser03Attributes().Run();
 new Setups.SetupRunAllManagementAgents().Run();

} 

Here's a sample of vbscript to execute a remote wmi call to MIIS. Just pass in the management agent Guid and the run profile name.

 Dim Locator
 Dim Service
 Dim ManagementAgent
 Dim MASet
 Dim MA
 Const PktPrivacy = 6
 Const wbemAuthenticationLevelPkt = 6
 Set Locator = CreateObject("WbemScripting.SWbemLocator")
 Locator.Security_.AuthenticationLevel = wbemAuthenticationLevelPkt
 Set Service = Locator.ConnectServer("[your server]", "root/MicrosoftIdentityIntegrationServer", "[domain\user]", "[password]")
 Set MASet   = Service.ExecQuery("select * from MIIS_ManagementAgent where Guid = '{" & WScript.Arguments(0) & "}'")
 for each MA in MASet
 MA.Execute(WScript.Arguments(1))
 next

 So, you're well on your way to a fully automated unit tests. Almost. The only thing you cannot automate (with MIIS) is the deletion of connector space objects. You must open the MIIS console, right-click every MA, choose delete, make sure it's 'delete connector space only', and click ok. I guess this is for the best, but 10 or so clicks is really nothing compared to the payback of automating the rest.

Last, I'll leave you with one challenge: stick with it. Again, there is an upfront investment but it's has an incredible return. Plus, it is always possible to automate a test. Ok, it's not always possible to automate a test, but you can still include the 'not-automatable' test in your unit test project with my handy-dandy 'human intervention' class.

public class HumanIntervention
{
 public static void Assert(string test)
 {
  DialogResult result = MessageBox.Show
   (test, "Human Involvement", MessageBoxButtons.YesNo);

  if (result == DialogResult.No)
  {
   throw new ApplicationException("A human involved test failed: " + test);
  }

 
 }

}

Using human intervention in your unit tests:


[Test]
public void MockUser03AddedToBlackBoxSystem()
{
 
 HumanIntervention.Assert("Was Mock User 03 successfully added to Black Box?");

}

Cheers!

Thomas 

ASP.Net 2.0 CS0716: Cannot convert to static type 'System.Web.Security.Roles'

I have a simple page which displays the current users active roles. Silly me, I thought a good name would be 'Roles'.

Turns out, this caused a namespace collision, I think, with the static class System.Web.Security.Roles. The namespace 'System.Web.Security' is included by default with ASP.NET 2.0 code-behinds. Also of interest, by default there are no namespace regions with code-behinds. Not wanting to stray from the typified lazy developer, I left this as-is. Hey, Microsoft knows best.

This error only appeared on the release to production, as I chose the 'Publish Web Site' deployment method, where I was using the 'Copy Web Site' deployment method to our QA server. For some reason, only the former method yields this error.

The fix? Changed 'Roles' to '_Roles'. Alternatively, I could have enclosed the class in a namespace.

Xml ThinNet API integration with JD Edwards, iWay Adapter

Anybody doing integration with JD Edwards? We are on OneWorld 8.0. We have purchased adapters from iWay Software but I am having some pains getting up and running, namely while attempting to connect through the JDEdwards adapter. I get the following exception:

Error getting target [JD Edwards] Failed to connect....com.jdedwards.system.net.JdeNetConnectionClosedException: Connection closed by server

I've done the setup work from the installation guide). I am have trouble getting support on this...anybody have a clue?

Also, I would like to establish contact with anyone in the community using iWay, whether or not for JD Edwards....maybe trade some ideas, war stories, that sort of thing.

Access to the path "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files...

I see a lot of trouble user have out there when using impersonation under ASP.NET (<identity impersonate=”true” />), namely something such as

Access to the path "C:\WINDOWS\Microsoft.NET\Framework\v1.1.4322\Temporary ASP.NET Files\root

 For our situation, we are using specified credentials ( e.g., <identity impersonate="true" userName="MYDOMAIN\applicationService" password="password" />) under IIS 6.0 using the default app pool.

Well, I was getting the above error as well, and the simplest way to handle it, I've found, is to add your identity to the IIS_WPG (IIS worker process group). This group seemed to come with all the necessary rights under both NTFS and the Local Security Policy.

 

 

Using Enterprise Library? Use a Post Build Event for pain-free config file copying

Using a post-build event can be a time saver. You can create command lines in the project property pages (right-click the project, say your winform or console, in the solution explorer and then choose 'Properties'). Under common properties, choose 'Build Events'. Then click the ellipsis beside Post-build Command Line.

In this dialog, you can run commands and call other programs. For this topic, enter this simple one-liner to have all those Enterprise Library config files nicely copied to your output directory:

copy $(ProjectDir)*.config $(ProjectDir)$(OutDir)

 

InfoPath hangs...boils down to Thread Identity/Thread Principal

As i toiled over this, I was sure there was some problem with the InfoPath Automation I was doing:

infoPathApplication = new ApplicationClass();

oXDocumentCollection = infoPathApplication.XDocuments;

oXDocument = oXDocumentCollection.NewFromSolution(psrTemplateUrl);

The symptom: InfoPath just hangs right on this line. Now, to put some context around it...

I found a nifty way to save our accountants tons of hours through the year by prefilling parts of the form. The accountants select a lookup key from a dropdown, and the associated rules run some directives that do some stuff. Anyway, I could automate all that for all accountants just by pluggin in the value right into the Xml Document Object Model used by InfoPath (more on that later). This process is invoked from a web service call, hence it runs in the ASP.NET context.  Additionally, to mock a Request-only message pattern (because this takes about an hour to run), I split off a worker thread so the main thread could return immediately.

The problem manifested when I deployed this solution to the dev server. First I got some security errors...Oh, I need to impersonate, so I do so in Web.config. Then, some more hanging. Oh! I need to login as my impersonated account and clear some of the IE warning dialogs. OK so far. This is when I split off a thread from the threadpool using the BeginInvoke pattern of the Delegate class. Again, more security errors occurred. After a quick Google search, I see that worker threads are now harnessed under the WindowsIdentity that harnessed them. So, one can use the WindowsImpersonationContext class to solve this (anywhere in code running under the ThreadPool thread):

WindowsIdentity winId = WindowsIdentity.GetCurrent();

WindowsImpersonationContext impersonationContext = winId.Impersonate();

Impersonation occurs until you undo it with:

impersonationContext.Undo();

At this point, more hair pulling because InfoPath continued to hang at the NewFromSolution() call. Finally, I thought to assign the thread the same principle as the WindowsPrincipal (which seemed to show up as the authenticated caller, not the impersonation context) - And voila, we're cooking with grease!

WindowsImpersonationContext impersonationContext = winId.Impersonate();

System.Security.Principal.WindowsPrincipal principal = new WindowsPrincipal(winId);

System.Threading.Thread.CurrentPrincipal= principal;

 

Oh, and for those of you still hangin' on for the InfoPath automation solution....

Remember that a control (named 'NameLookUp) had some rules on it. The rules will execute when you set the .text property through automation.

You will need to reference 2 assemblies

1) Microsoft Office InfoPath 1.0 Type Library. You can find this in the COM section of your add reference dialog

2) Microsoft.Office.Interop.InfoPath.Xml. To find this gem, browse to:C:\Program Files\Microsoft Office\OFFICE11\Microsoft.Office.Interop.InfoPath.Xml.dll

 

It is as simple as this (the setup code from above applies):

using Microsoft.Office.Interop.InfoPath;

Microsoft.Office.Interop.InfoPath.Xml.IXMLDOMNode oProjectNumber;

oProjectNumber = oXDocument.DOM.selectSingleNode("//my:NameLookUp");

oProjectNumber.text = projectNumber;

oXDocument.SaveAs(savePath + fileName);  //here i am just saving the prefilled form for the accountants.

 

 

 

 

 

 

A Builder pattern for repeated string concatenation with separators

ever needed to something like this:

for(int i=0;i<names.Length;i++)

{

  displayNames+= names[i].ToString +“, “;

}

displayNames = displayNames.SubString(displayNames.Length-2, 2);

 

That stinks having to worry about that last concatentation. Builder pattern to the rescue. This class lets you concatenate strings with any character or string and never outputs the junk at the end.

here is a usage example:

ConcatBuilder commaConcat = ConcatBuilder.CreateCustomConcatBuilder(“, “);

commaConcat.Add(“slim“);

commaConcat.Add(“lightweight“);

commaConcat.Add(“attractive“);

return commaConcat.ToString();  //returns “slim, lightweight, attractive“

 

 

 

 

<code>

using System;

using System.Collections.Specialized;

using System.Text;

namespace x

{

/// <summary>

/// A Builder for concatenating strings with separator strings.

/// Simplifies checking for length and null of each part before

/// applying the separator

/// </summary>

public class ConcatBuilder

{

string _separator = " - ";

StringCollection _parts = new StringCollection();

private ConcatBuilder()

{

}

private ConcatBuilder(string separator)

{

_separator = separator;

}

public static ConcatBuilder CreateHyphenConcatBuilder()

{

return new ConcatBuilder();

}

public static ConcatBuilder CreateCustomConcatBuilder(string separator)

{

return new ConcatBuilder(separator);

}

public void Add(string part)

{

if(null != part && part.Trim() != string.Empty)

_parts.Add(part);

}

public void Clear()

{

_parts.Clear();

}

public override string ToString()

{

StringBuilder sb = new StringBuilder();

for(int i=0; i < _parts.Count; i++)

{

sb.Append(_parts[i]);

if(i < _parts.Count -1) sb.Append(_separator);

}

return sb.ToString();

}

 

 

}

}


 

</code>

 

 

 

 

nifty class that abstracts an interval or range

the following class cleans up ugly code that tests whether value a is between value b and value c. it works with any class implementing IComparable.

so stuff like this:

if(myDate >= startDate && myDate  <= endDate) ..

becomes this:

if(goodInterval.IsInInterval(myDate))

=========================

 

<code>

using System;

namespace x.Utility
{
 public enum IntervalComparison
 {
  Inclusive =0,
  Exclusive
 }

 /// <summary>
 /// Provides interval needs, defaults to Inclusive on start and end
 /// </summary>
 public class Interval
 {
  private IComparable _start;
  private IComparable _end;
  private IntervalComparison _startComparison = IntervalComparison.Inclusive;
  private IntervalComparison _endComparison = IntervalComparison.Inclusive;

  public Interval(IComparable start, IComparable end)
  {
   _start = start;
   _end = end;

  }

  public Interval(IComparable start, IComparable end, IntervalComparison startComparison, IntervalComparison endComparison)
  {
   _start = start;
   _end = end;
   _startComparison = startComparison;
   _endComparison = endComparison;

  }

  public IComparable IntervalStart
  {
   get{ return _start; }
  }

  public IComparable IntervalEnd
  {
   get{ return _end; }
  }

  public bool IsInInterval(IComparable comparable)
  {
   if(_startComparison == IntervalComparison.Inclusive)
   {
    if(1 == _start.CompareTo(comparable)) return false;
   }

   if(_startComparison == IntervalComparison.Exclusive)
   {
    if(-1 != _start.CompareTo(comparable) ) return false;
   }
   
   if(_endComparison == IntervalComparison.Inclusive)
   {
    if(0 > _end.CompareTo(comparable) ) return false;
   }

   if(_endComparison == IntervalComparison.Exclusive)
   {
    if(1 != _end.CompareTo(comparable) ) return false;
   }

   return true;


  }

 }
}

</code>

 

 

are BizTalk and InfoPath in diametric opposition regarding web authentication and authorization?

scratching my head for the past few days...

in our current SOA migration, i am running into an intrestring quandry. take a composable web service, GetStatusReports. so just how can i provide a suitable authentication and authorization schemes that agree with InfoPath AND BizTalk, where both are consumers of this service?

InfoPath:

Supports Integrated Windows Authentication

Lacks Web Service Extensions (WSE 2.0)

Custom authentication may not be suitable, as there is no secure place to store credentials.

 

BizTalk:

Supports Web Service Extensions (WSE 2.0) with a microsoft-supported adapter

Lacks Integrated Windows Authentication (as far as i can tell, 401 forbidden returned if anonymous disallowed)

Custom authentication may be suitable.

 

What other options have i missed? Right now, I can only run the service under ASP.NET impersonation with a domain user account.