1

I've got a requirement to protect my business object properties via a list of separate authorization rules. I want my authorization rules to be suspended during various operations such as converting to DTOs and executing validation rules (validating property values the current user does not have authorization to see).

The approach I'm looking at wraps the calls in a scope object that uses a [ThreadStatic] property to determine whether the authorization rules should be run:

public class SuspendedAuthorizationScope : IDisposable
{
    [ThreadStatic]
    public static bool AuthorizationRulesAreSuspended;

    public SuspendedAuthorizationScope()
    {
        AuthorizationRulesAreSuspended = true;
    }

    public void Dispose()
    {
        AuthorizationRulesAreSuspended = false;
    }
}

Here is the IsAuthorized check (from base class):

public bool IsAuthorized(string memberName, AuthorizedAction authorizationAction)
{
    if (SuspendedAuthorizationScope.AuthorizationRulesAreSuspended)
        return true;

    var context = new RulesContext();

    _rules.OfType<IAuthorizationRule>()
        .Where(r => r.PropertyName == memberName)
        .Where(r => r.AuthorizedAction == authorizationAction)
        .ToList().ForEach(r => r.Execute(context));

    return context.HasNoErrors();
}

Here is the ValidateProperty method demonstrating usage (from the base class):

private void ValidateProperty(string propertyName, IEnumerable<IValidationRule> rules)
{
    using (new SuspendedAuthorizationScope())
    {
        var context = new RulesContext();
        rules.ToList().ForEach(rule => rule.Execute(context));
         if (HasNoErrors(context))
            RemoveErrorsForProperty(propertyName);
        else
            AddErrorsForProperty(propertyName, context.Results);
    }
    NotifyErrorsChanged(propertyName);   
}

I've got some tests around the scoping object that show that the expected/correct value of SuspendedAuthorizationScope.AuthorizationRulesAreSuspended is used as long as a lambda resolves in the scope of the using statement.

Are there any obvious flaws to this design? Is there anything in ASP.NET that I should be concerned with as far as threading goes?

agartee
  • 445
  • 2
  • 15
  • 1
    If you are depending on knowing the thread that these rules execute under, then you've got a problem. You should do your best to have no dependency on threads in ASP.NET. Perhaps you want to scope these rules to the Session? – John Saunders Mar 16 '14 at 02:32
  • rules.ToList().ForEach(...) will ensure I am in the current thread, correct? – agartee Mar 16 '14 at 02:43
  • The ThreadStatic value could be set on one thread, but read on another. – John Saunders Mar 16 '14 at 03:36
  • I'm counting on it being a different value on another thread. – agartee Mar 16 '14 at 21:37

3 Answers3

2

There are two concerns that I see with your proposed approach:

  1. One's failure to use using when creating SuspendedAuthorizationScope will lead to retaining open access beyond intended scope. In other words, an easy to make mistake will cause security hole (especially thinking in terms of future-proofing your code/design when a new hire starts digging in unknown code and misses this subtle case).
  2. Attaching this magic flag to ThreadStatic now magnifies the previous bullet by having possibility of leaving open access to another page since the thread will be used to process another request after it's done with the current page, and its authorization flag has not been previously reset. So now the scope of authorization lingering longer than it should goes not just beyond missing call to .Dispose(), but actually can leak to another request / page and of completely different user.

That said, the approaches I've seen to solving this problem did involve essentially checking the authorization and marking a magic flag that allowed bypass later on, and then resetting it.

Suggestions: 1. To at least solve the worst variant (#2 above), can you move magic cookie to be a member of your base page class, and have it an instance field that is only valid to the scope of that page and not other instances? 2. To solve all cases, is it possible to use Functors or similar means that you'd pass to authorization function, that would then upon successful authorization will launch your Functor that runs all the logic and then guarantees cleanup? See pseudo code example below:

void myBizLogicFunction()
{
   DoActionThatRequiresAuthorization1();
   DoActionThatRequiresAuthorization2();
   DoActionThatRequiresAuthorization3();
}

void AuthorizeAndRun(string memberName, AuthorizedAction authorizationAction, Func privilegedFunction)
{
    if (IsAuthorized(memberName, authorizationAction))
    {
        try
        {
            AuthorizationRulesAreSuspended = true;

            privilegedFunction();
        }
        finally
        {
            AuthorizationRulesAreSuspended = true;
        }
    }
}

With the above, I think it can be thread static as finally is guaranteed to run, and thus authorization cannot leak beyond call to privilegedFunction. I think this would work, though could use validation and validation by others...

LB2
  • 4,802
  • 19
  • 35
  • The "base class" references in my questions refer to my base DomainModel, as this is a multi-platform application and I don't want different authorization strategies for each platform (web, WPF, service). Does that make any difference here? – agartee Mar 16 '14 at 02:57
  • @agartee I guess where I'm confused a bit is how your business objects check for authorization and reject call if needed authorization is not present? – LB2 Mar 16 '14 at 03:09
  • I'm using a Windsor interceptor (AOP) that gets attached in the creation of the domain model via a factory method. – agartee Mar 16 '14 at 21:19
2

If you have complete control over your code and don't care about hidden dependencies due to magic static value you approach will work. Note that you putting big burden on you/whoever supports your code to make sure there is never asynchronous processing inside your using block and each usage of magic value is wrapped with proper using block.

In general it is bad idea because:

  • Threads and requests are not tied one-to one so you can run into cases when you thread local object is changing state of some other request. This will even more likely to happen in you use ASP.Net MVC4+ with async handlers.
  • static values of any kind are code smell and you should try to avoid them.

Storing request related information should be done in HttpContext.Items or maybe Session (also session will last much longer and require more careful management of cleaning up state).

Alexei Levenkov
  • 98,904
  • 14
  • 127
  • 179
  • There are lots of ways people can do incorrect things in code. In my case, it would just not make any sense to disable authorization checks when running async methods. Disabling auth checks in my case is only necessary for validation (in which case the base class handles it and other developers would not need to write any code for this because its already done) and when converting business objects/domain models to DTOs or EF entities, in which case there would never be anything async going on. – agartee Mar 16 '14 at 21:33
  • The same limitations you're talking about would also apply to a TransactionScope, which this design is based on. – agartee Mar 16 '14 at 21:35
  • @agartee I don't believe `TransactionScope` relies on static (or thread static) values. But I easily could be wrong as I'm not familiar with that code. – Alexei Levenkov Mar 16 '14 at 21:43
  • @AlexeiLevenkov, `TransactionScope` does use TLS, unless its construtor is called with `TransactionScopeAsyncFlowOption.Enabled` option. – noseratio Mar 16 '14 at 23:17
0

My concern would be about the potential delay between the time you leave your using block and the time it takes the garbage collector to get around to disposing of your object. You may be in a false "authorized" state longer than you intend to be.

jgartee
  • 1
  • 1
  • 1
  • This is only a problem if `using` is not used (see my answer post for more detail). If `using` is used, then it guarantees that `.Dispose()` is called as part of leaving the code block, and not when garbage collector decides to get to it. – LB2 Mar 16 '14 at 02:54