RavenDB has an really good extensibility support that is based on Bundles and Responders. Bundles can be seen as plugins they are the high level extensibility API. RavenDB comes with a lot of built-in bundles and it is pretty easy to create our own.

On the other side responders are the low level API to extend RavenDB. If you are used to ASP.Net a responder can be compared to an HttpHandler, a responder in RavenDB is bound to a URI as in the following sample:

public class MyResponder : AbstractRequestResponder
{
    public override string UrlPattern
    {
        get { return "^/my/(.+)"; }
    }

    public override string[] SupportedVerbs
    {
        get { return new string[] { "GET" }; }
    }

    public override void Respond( IHttpContext context )
    {
        var transactionInformation = GetRequestTransaction( context );

        using ( Database.DisableAllTriggersForCurrentThread() )
        {
            var result = new 
            {
                MyData = "Hi, there"
            };

            context.WriteJson( result );
        }
    }
}

if we build our assembly and drop it in the “plugins” folder of the RavenDB server we can invoke our responder using the following URL: http://raven-server-url/databases/myDatabaseName/my

The database name in the URL identifies for which database we want to trigger the responder and the “my” segment identifies, in this specific case, our responder since “my” matches the UrlPattern property of the responder itself. As we saw in the above sample we can specify http verbs we want to support and we also saw that we can write our response to the http response stream.

A real world usage: UserPermissions responder

RavenDB comes with a really amazing bundle, the Authorization Bundle, that let us define, in a really granular and flexible ways documents permissions.

If we are developing a client, rich client, application such as a SPA with AngularJS we miss the ability to get in one single request the whole permission set of the current user.

Q: why we need it?
A: simply because we want the client application to know upfront if a button, for example, should be hidden, or disabled, because the current user cannot perform that specific request.

We will obviously perform security checks also on the server but we want a user friendly UI that prevents the user to issue non allowed requests and we do not want to ask to the server user permissions for each element on the UI, we want all the permissions up front.

And…before you ask: we don’t really care if security settings change server side and the client is not immediately up-to-date because we know that security checks are performed on the server too.

we want to be able to issue a request such as http://raven-server-url/databases/myDatabaseName/security/UserPermissions/user-id to get all the user permissions taking into account the following facts:

  • The authorization bundle has the concept of permission priority, we want permissions ordered by priority;
  • The authorization bundle has the concept of users and roles and roles can be nested in roles, we want a flat list of permissions that honors the hierarchical relationship of the roles;

We start by defining the result we want:

public class UserPermissionsResult
{
    public String UserId { get; set; }
    public IEnumerable<OperationPermission> Permissions { get; set; }
}

Where the OperationPermission type is defined by the Authorization Bundle. The second thing we need is a standard way to compare permissions, a way that consider the whole permission structure:

class OperationPermissionComparer : IEqualityComparer<OperationPermission>
{
    public bool Equals( OperationPermission x, OperationPermission y )
    {
        var sameOp = x.Operation == y.Operation;
        var sameAllow = x.Allow == y.Allow;
        var samePriority = x.Priority == y.Priority;
        var sameTagCount = x.Tags.Count == y.Tags.Count;
        var sameTags = x.Tags.Select( t => t.Trim().ToLowerInvariant() )
            .All( xt => y.Tags.Select( yt => yt.Trim().ToLowerInvariant() ).Contains( xt ) );

        return sameOp && sameAllow && samePriority && sameTagCount && sameTags;
    }

    public int GetHashCode( OperationPermission obj )
    {
        var builder = new HashCodeBuilder( typeof( OperationPermission ).GetHashCode() );
        builder.AddObject( obj.Operation );
        builder.AddObject( obj.Allow );
        builder.AddObject( obj.Priority );
        foreach ( var t in obj.Tags )
        {
            builder.AddObject( t.Trim().ToLowerInvariant() );
        }

        return builder.CombinedHash32;
    }
}

The HashCodeBuilder is provided by the Radical toolkit.

We can now easily glue everything together creating our responder:

public class UserPermissionsResponder : AbstractRequestResponder
{
    static readonly OperationPermissionComparer operationPermissionComparer = new OperationPermissionComparer();

    public override string UrlPattern
    {
        get { return "^/security/UserPermissions/(.+)"; }
    }

    public override string[] SupportedVerbs
    {
        get { return new string[] { "GET" }; }
    }

    public override void Respond( IHttpContext context )
    {
        var match = urlMatcher.Match( context.GetRequestUrl() );
        var userId = match.Groups[ 1 ].Value;

        var transactionInformation = GetRequestTransaction( context );

        if ( String.IsNullOrWhiteSpace( userId ) )
        {
            context.SetStatusToBadRequest();
            return;
        }

        using ( Database.DisableAllTriggersForCurrentThread() )
        {
            var user = this.GetDocumentAsEntity<AuthorizationUser>( userId );
            if ( user == null )
            {
                context.SetStatusToBadRequest();
                return;
            }

            // permissions on user
            var permissions = from permission in user.Permissions
                                select permission;

            // permissions on all user's roles
            permissions = permissions.Concat(
                from roleName in GetHierarchicalNames( user.Roles )
                let role = this.GetDocumentAsEntity<AuthorizationRole>( roleName )
                where role != null
                from permission in role.Permissions
                select permission
            );

            var orderedPermissions = permissions.Distinct( operationPermissionComparer )
                .OrderByDescending( x => x.Priority )
                .ThenBy( x => x.Allow );

            var result = new UserPermissionsResult()
            {
                UserId = userId,
                Permissions = orderedPermissions.ToList()
            };

            context.WriteJson( result );
        }
    }

    private static string GetParentName( string operationName )
    {
        int lastIndex = operationName.LastIndexOf( '/' );
        if ( lastIndex == -1 )
            return "";
        return operationName.Substring( 0, lastIndex );
    }

    private static IEnumerable<string> GetHierarchicalNames( IEnumerable<string> names )
    {
        var hierarchicalNames = new HashSet<string>( StringComparer.InvariantCultureIgnoreCase );
        foreach ( var name in names )
        {
            var copy = name;
            do
            {
                hierarchicalNames.Add( copy );
                copy = GetParentName( copy );
            } while ( copy != "" );
        }
        return hierarchicalNames;
    }

    private T GetDocumentAsEntity<T>( string documentId ) where T : class
    {
        var document = this.Database.Get( documentId, null );
        if ( document == null )
            return null;
        var entity = document.DataAsJson.JsonDeserialization<T>();
        return entity;
    }
}

Besides the fact that the code is long we are doing some really simple steps:

  • Using the built-in URL matcher we retrieve the supplied user id
  • We disable all triggers in the database during our handling process;
  • We iterate over all the user permissions, all the roles and nested roles permissions building the required list;
  • We end sorting the list as the client expects it;

One thing to notice here is that even if we are server side, we are running in the RavenDB server process we are not forced to deal with raw json objects but we can de-serialize them into well-known C# types.

.m