Version 4 (modified by smagi, 10 years ago)

--

Clavis: Security, Type-safe URLs for ASP.NET

Clavis is a simple class library for use with ASP.NET. It provides primitives for secure parameter passing between pages, with compiler-verified taint-checking for any insecure parameters. For an overview of the rationale or the operational details behind Clavis, see  the Clavis blog posts.

You need only understand a few concepts to use Clavis:

  1. IContinuation for specifying page parameter types
  2. Unsafe<T> for specifying unsafe/insecure page parameters
  3. Continuation.ToUrl overloads for generating URLs from continuations + arguments
  4. Continuation.TryParseX for parsing page parameters

Typed Page Parameters

A typed page parameter list is specified via an IContinuation<...> declaration, like so:

public class SomePage : System.Web.Page, IContinuation<int, string>
{
  ...
}

This declares a page that accepts a protected Int32 as the first argument, and a protected string as the second argument. By default, all types specified in an IContinuation<...> declaration will be protected, which means they cannot be changed by clients.

Any type can appear as an argument to IContinuation<...>, not just primitive values. In fact, it's good practice in Clavis not to use primitive types since parameter names are generated from the class name by default:

public class SomePage : System.Web.Page, IContinuation<Project, Customer>
{
  ...
}

Clavis can also handle lists of values by specifying IEnumerable<T> as a parameter type:

public class SomePage : System.Web.Page, IContinuation<IEnumerable<Project>, Customer>
{
  ...
}

Unsafe Parameters

If you wish to declare that a certain page parameter is unprotected, then you need only wrap it with Unsafe<T>. For instance, suppose the integer argument from the first example should be unprotected:

public class SomePage : System.Web.Page, IContinuation<Unsafe<int>, string>
{
  ...
}

IEnumerable<T> and Unsafe<T> can also be nested, so you can have an unsafe list of objects as a parameter:

public class SomePage : System.Web.Page, IContinuation<Unsafe<IEnumerable<Project>>, Customer>
{
  ...
}

Generating URLs

You can easily generate a URL from a continuation with its arguments like so:

var url = Continuation.ToUrl<SomePage, int, string>(
              3.AsParam(), "hello world!".AsParam());

The first type argument, SomePage, is the continuation type. The subsequent type arguments are the type arguments to IContinuation<...>. Another method of generating a URL that requires fewer type annotations:

var url = Continuation.Params(3.AsParam(), "hello world!".AsParam());
                      .ToUrl<SomePage>();

The Param.AsParam() extension methods are fully defined over all  IConvertible types. Types that aren't IConvertible require that you provide an ICovertible type as a key:

public class SomePage : System.Web.Page, IContinuation<Project, Customer> { ... }

...

var url = Continuation.Params(project.AsParam(project.Id), customer.AsParam(customer.Id));
                      .ToUrl<SomePage>();

Parsing Page Parameters

Inside the continuation, you can obtain access to page parameter values via the Param.TryParseX overloads, where X is the index of the parameter in the IContinuation<...> specification:

public class SomePage : System.Web.Page, IContinuation<int, string>
{
  int arg0;
  string arg1;

  override protected void OnInit(EventArgs e)
  {
    if (this.TryParse0(out arg0)); //do something with arg0
    if (this.TryParse1(out arg1)); //do something with arg1
  }
}

The above will only work if the types are IConvertible. For non-IConvertible values, you instead need to parse an Id<TKey, TType>:

public class SomePage : System.Web.Page, IContinuation<Project, Customer>
{
  Project arg0;
  Customer arg1;

  override protected void OnInit(EventArgs e)
  {
    Id<int, Project> id0;
    if (this.TryParse0(out id0))
      arg0 = SomeDb.Projects.Single(x => x.Id == id0.Key);

    Id<int, Customer> id1;
    if (this.TryParse1(out arg1))
      arg1 = SomeDb.Customers.Single(x => x.Id == id1.Key);
  }
}