State driven security in LightSwitch (part 3): server side state transition security

Introduction

This post of mainly about the server side. We want to come up with a nice piece of base infrastructure with which we can protect server side the state transition of a certain entity. The base infrastructure should be treatable as a black box and should be easily consumable and easy to setup inside a LightSwitch project where state matters.

Although what follows is all about the server side, I might improve the understanding to first see things on the client: we want that when a user makes a state transition which is not allowed, she get a nice error message:

 

InvalidTransition

 

The message is clear: the user tried to change state from draft directly to approved, which is not allowed. Obviously, we add later on the necessary functionality so that client side the user could not even try to do this transition.

 

Where do we want to validate the state transition?

Obviously server side, because that’s the place where we protect our assets. The best place to trigger this validation is in the Validate method of the entity which carries the state. In our example the state property is part of the entity which is monitored by the state the HolidayRequest entity.

 partial void HolidayRequests_Validate(HolidayRequest entity, 
            EntitySetValidationResultsBuilder results)
        {
            StateManagement.ValidateStateTransition(
                entity,
                s => s.HolidayStateCode, 
                this.DataWorkspace.ApplicationData.StateCodes, 
                results);
        }

That’s a very simple call into the black box. Let’s first inspect the method signature:

 public static void ValidateStateTransition<T, TSource, TProperty>(
            TSource entityContainingTheState,
            Expression<Func<TSource, TProperty>> statePropertyLambda,
            T stateCodes,
            EntitySetValidationResultsBuilder results, bool ignoreReflexiveTransitions = true)
            where T : IEntitySet
            where TSource : IEntityObject
        {
}

The signature has some generic parameters, but luckily we don’t need to specify the generic types when calling the method (they are auto discovered) :)

In case you want to go away now, think again,  this was the most difficult part. I’ll explain the internals of the ValidateStateTransition method now, but you don’t need to understand all details, you can use the method as a black box !

This simple method call is doing all the heavy lifting for us. Obviously it needs to know  where the state property is stored and it needs to know the name of the field containing the state (we do this via a lamdba expression in such a way things are strongly typed !). It needs also to know where the list of potential states are. The method has a optional parameter “ignoreReflexiveTransitions”. A reflexive transition is a transition from state A to state A. Kind of null transition if you want. Being able to capture such a transition can be important in very specific cases, but for most cases you just want to ignore this transition (that’s why the default value is true).

Let’s take a close look now to the ValidateStateTransition method:

 public static void ValidateStateTransition<T, TSource, TProperty>(
            TSource entityContainingTheState,
            Expression<Func<TSource, TProperty>> statePropertyLambda,
            T stateCodes,
            EntitySetValidationResultsBuilder results, bool ignoreReflexiveTransitions = true)
            where T : IEntitySet
            where TSource : IEntityObject
        {
            var stateProperty = entityContainingTheState.GetEntityTrackedProperty(statePropertyLambda);
            var requestedStateCode = stateProperty.Value as IStateCode;

            if (requestedStateCode == null || entityContainingTheState.Details.EntityState == EntityState.Added)
            {
                string initialStateValue = StateManagement.GetInitialStateValue(stateCodes);
                if (requestedStateCode == null || requestedStateCode.StateValue != initialStateValue)
                {
                    results.AddEntityError(string.Format("The initial state must be {0}", initialStateValue));
                }
            }
            else
            {
                var originalStateCode = stateProperty.OriginalValue as IStateCode;

                bool stateChanged = !(originalStateCode.StateValue == requestedStateCode.StateValue);
                if (stateChanged || ignoreReflexiveTransitions == false)
                {
                    bool transitionIsAllowed
                        = StateManagement.IsStateTransitionAllowed(originalStateCode.StateValue, requestedStateCode.StateValue);
                    if (!transitionIsAllowed)
                    {
                        results.AddEntityError(string.Format("transition not allowed from {0} to {1}",
                            originalStateCode.StateValue, requestedStateCode.StateValue));
                    }
                }
            }

        }

It’s clear that the ValidateStateTransition method needs as input the current state and the requested state, but what is the current state? This is of course the state currently in the database. The state mentioned in the entity is the requested state. We don’t need to do an additional database call to get the current state value. LightSwitch is doing this already for us. We can get the “original” value of each  EntityTracked property by means of following extension method:

public static IEntityTrackedProperty GetEntityTrackedProperty<TSource, TProperty>(
           this TSource entity,
           Expression<Func<TSource, TProperty>> propertyLambda) where TSource : IEntityObject
        {
            string fieldName = entity.GetEntityObjectPropertyName(propertyLambda);
            return entity.Details.Properties[fieldName] as IEntityTrackedProperty;
        }

I make use here of an other extension method, GetEntityObjectPropertyName, I documented in a previous post. So, the extension method returns us based on a lambda expression an IEntityTrackedProperty. We’ll need this to get access to the original value of the state field.

In a first block we’ll check if there is effectively a state in the entity and if we check in the case the entity itself in “create” mode, that the state is given the mandatory initial state which we can find in our StateCode table. (remember the field “IsInitialState” is part of our IStateCode interface).

For the rest, the logic of this method is very straightforward: we’ll check if the transition is allowed and if not, we’ll fill the ValidationResult object with a nice error message. Note also the logic for the reflexive transitions.

As explained earlier, we’ll use the build-in permission mechanism of LightSwitch to keep track of the allowed transitions:

statepermissions

The permissions follow a strict naming scheme. By doing so we can use it directly in code:

    private static readonly string StateTransitionPermissionFormat = "CanDoTransitionFrom{0}To{1}";

The current schema presumes that we only have one state aware entity in our app. Of course the format can be easily extended to support more state aware entities: “CanDo{0}TransitionFrom{1}To{2}”. where {0} can be replaced by the state aware entity (e.g. HolidayRequest).

Now we are close to the actual verification of the state transition, which is really as simple as possible but no simpler:

public static bool IsStateTransitionAllowed(string currentState, string requestedState)
        {
            string requiredPermission
                = string.Format(StateTransitionPermissionFormat, currentState, requestedState);

            bool transitionIsAllowed
                = Application.Current.User.HasPermission("LightSwitchApplication:" + requiredPermission);
            return transitionIsAllowed;
        }

The method for retrieving the InitialState goes as follows:

public static string GetInitialStateValue<T>(T stateCodes) where T : IEntitySet
        {
            var stateList = new List<IStateCode>();
            //no better way currently than using a temp collection

            foreach (IStateCode item in stateCodes)
            {
                stateList.Add(item);
            }

            var initialStateRecord =
               stateList.Where(s => s.IsInitialState == true).Single(); // there must be one IsInitialState=true

            return initialStateRecord.StateValue;
        }

Consume the permissions in a specific role

Let’s configure now, as an example the permissions for the Manager role.  It’s always a good practice to limit the permissions of managers to the strictest mininum :) In our example they only get the permission to approve or reject a holiday request:

managerpermissions

What’s the advantage of this approach?

It’s cool because there is no magic involved, it simply uses all goodness of LightSwitch, nothing more and nothing less.

Adding a new transition only boils down to adding a new entry in the permission table. Do you know that you even do not need to recompile the application when you would be need to add a new transition? This is since we don’t have any hard coded reference to our transitions inside the application. Simply add the the new permission directly in the database table.  Of course when you don’t use the permission designer, the permission will not be available in the LightSwitch security screens, where you can compose a role by selecting the various permissions.

An alternative approach could be to add upfront all theoretically possible permissions, so the mathematical product set. In case you have 3 permissions (A,B,C) that would be

  • CanDoTransitionFromAToB
  • CanDoTransitionFromAToC
  • CanDoTransitionFromBToA
  • CanDoTransitionFromBToC
  • CanDoTransitionFromCToA
  • CanDoTransitionFromCToB

Of course for large state diagrams, this can become cumbersome, so I would simply stick to the first approach.

The most important advantage is that the call to the ValidateStateTransition method is a nice black box, but it is not really relying on a whole series of assumptions nor conventions.

  • The only code prerequisite is that that the field which carries the state must be an entity which implement IStateCode, that’s all.
  • Secondly we need of course the permission table setup and here we need to follow the naming convention of the state transition permissions we defined in code ( CanDoTransitionFrom{0}To{1}

What could still be improved?

The only magic strings we have now are in the permission table and they are functionally dependent on the values in our StateCode table. The permissions are inside the project model file (the .lsml file). I have done already some research on how to generate strongly typed material from the model file. See my post on strongly typed code values. In this approach I’m reading from the model file. In case we would like to improve this state transition permission approach, it could be interesting to write the permissions directly into the model file based on the information coming from a permission designer. Would be cool, but I’m quite reluctant to do so. I think that the model file is the domain of the LightSwitch product team, we should not touch it :)

What’s next?

We have now a secure server side framework method for the state transitions. How will we consume this client side and add some comfort so that the user can not select state transitions which are not allowed.