3

I detected a problem in the RequestFilter execution order.

The ValidationFeature in ServiceStack is a Plugin that just registers a Global Request Filter. The Order of Operations points out that Global Request Filters are executed after Filter Attributes with a Priority <0 and before Filter Attributes with a Priority >=0

My BasicAuth filter has -100 priority, and in fact everything goes well if the Service is annotated at class level, but it fails when the annotation is at method level, with the authentication filter being executed after.

I am using 3.9.70 Is there any quick fix for this? Thanks

Scott
  • 21,211
  • 8
  • 65
  • 72
Cristóvão
  • 255
  • 1
  • 2
  • 11

1 Answers1

4

When you add the annotation at method level then you are creating an Action Request Filter (because you are adding the annotation to an action method) which in the Order of Operations is operation 8, after the other filters have run.

5: Request Filter Attributes with Priority < 0 gets executed
6: Then any Global Request Filters get executed
7: Followed by Request Filter Attributes with Priority >= 0
8: Action Request Filters (New API only)


The best workaround I can suggest is to reconsider your service structure. I imagine you are having these difficulties because you are adding unauthenticated api methods alongside your secure api methods, and thus are using method level attributes to control authentication. So you are presumably doing something like this Your classes and attributes will be different, this is just exemplar:

public class MyService : Service
{
    // Unauthenticated API method
    public object Get(GetPublicData request)
    {
        return {};
    }

    // Secure API method
    [MyBasicAuth] // <- Checks user has permission to run this method
    public object Get(GetSecureData request)
    {
        return {};
    }
}

I would do this differently, and separate your insecure and secure methods into 2 services. So I use this:

// Wrap in an outer class, then you can still register AppHost with `typeof(MyService).Assembly`
public partial class MyService
{
    public class MyPublicService : Service
    {
        public object Get(GetPublicData request)
        {
            return {};
        }
    }

    [MyBasicAuth] // <- Check is now class level, can run as expected before Validation
    public class MySecureService : Service
    {
        public object Get(GetSecureData request)
        {
            return {};
        }
    }
}

Solution - Deferred Validation:

You can solve your execution order problem by creating your own custom validation feature, which will allow you to defer the validation process. I have created a fully functional self hosted ServiceStack v3 application that demonstrates this.

Full source code here.

Essentially instead of adding the standard ValidationFeature plugin we implement a slightly modified version:

public class MyValidationFeature : IPlugin
{
    static readonly ILog Log = LogManager.GetLogger(typeof(MyValidationFeature));
    
    public void Register(IAppHost appHost)
    {
        // Registers to use your custom validation filter instead of the standard one.
        if(!appHost.RequestFilters.Contains(MyValidationFilters.RequestFilter))
            appHost.RequestFilters.Add(MyValidationFilters.RequestFilter);
    }
}

public static class MyValidationFilters
{
    public static void RequestFilter(IHttpRequest req, IHttpResponse res, object requestDto)
    {
        // Determine if the Request DTO type has a MyRoleAttribute.
        // If it does not, run the validation normally. Otherwise defer doing that, it will happen after MyRoleAttribute.
        if(!requestDto.GetType().HasAttribute<MyRoleAttribute>()){
            Console.WriteLine("Running Validation");
            ValidationFilters.RequestFilter(req, res, requestDto);
            return;
        }

        Console.WriteLine("Deferring Validation until Roles are checked");
    }
}

Configure to use our plugin:

// Configure to use our custom Validation Feature (MyValidationFeature)
Plugins.Add(new MyValidationFeature());

Then we need to create our custom attribute. Your attribute will be different of course. The key thing you need to do is call ValidationFilters.RequestFilter(req, res, requestDto); if you are satisfied the user has the required role and meets your conditions.

public class MyRoleAttribute : RequestFilterAttribute
{
    readonly string[] _roles;

    public MyRoleAttribute(params string[] roles)
    {
        _roles = roles;
    }

    #region implemented abstract members of RequestFilterAttribute

    public override void Execute(IHttpRequest req, IHttpResponse res, object requestDto)
    {
        Console.WriteLine("Checking for required role");

        // Replace with your actual role checking code
        var role = req.GetParam("role");
        if(role == null || !_roles.Contains(role))
            throw HttpError.Unauthorized("You don't have the correct role");

        Console.WriteLine("Has required role");

        // Perform the deferred validation
        Console.WriteLine("Running Validation");
        ValidationFilters.RequestFilter(req, res, requestDto);
    }

    #endregion
}

For this to work we need to apply our custom attribute on the DTO route not the action method. So this will be slightly different to how you are doing it now, but should still be flexible.

[Route("/HaveChristmas", "GET")]
[MyRole("Santa","Rudolph","MrsClaus")] // Notice our custom MyRole attribute.
public class HaveChristmasRequest {}

[Route("/EasterEgg", "GET")]
[MyRole("Easterbunny")]
public class GetEasterEggRequest {}

[Route("/EinsteinsBirthday", "GET")]
public class EinsteinsBirthdayRequest {}

Then your service would look something like this:

public class TestController : Service
{
    // Roles: Santa, Rudolph, MrsClaus
    public object Get(HaveChristmasRequest request)
    {
        return new { Presents = "Toy Car, Teddy Bear, Xbox"  };
    }

    // Roles: Easterbunny
    public object Get(GetEasterEggRequest request)
    {
        return new { EasterEgg = "Chocolate" };
    }

    // No roles required
    public object Get(EinsteinsBirthdayRequest request)
    {
        return new { Birthdate = new DateTime(1879, 3, 14)  };
    }
}
  • So when we call the route /EinsteinsBirthday which does not have a MyRole attribute the validation will be called normally, as if using the standard ValidationFeature.

  • If we call the route /HaveChristmas?role=Santa then our validation plugin will determine that the DTO has our attribute and not run. Then our attribute filter triggers and it will trigger the validation to run. Thus the order is correct.

Screenshot

Community
  • 1
  • 1
Scott
  • 21,211
  • 8
  • 65
  • 72
  • Thanks for pointing out that detail about the action request filter. In that case, that is not a bug. The use case is really not about having unauthenticated methods. It is because, different methods differ on what roles they require. I discovered a workaround for this, by annotating the RequestDto and not the method. I guess in that case it is treated as a Request Filter attribute, right? – Cristóvão Jan 21 '14 at 21:42
  • @Blitzkrieg Yeah that's absolutely right, when the attribute is applied on the class then it's a Request Filter Attribute. I have updated my answer with how I would treat the role attribute on the action method requirement. – Scott Jan 21 '14 at 22:46
  • @Blitzkrieg I have updated the answer with a solution to running the validation after you have checked for the required roles. Full code for the [demo is here](https://gist.github.com/scottmcarthur/8557022). – Scott Jan 22 '14 at 11:44
  • Cool one scott. Nice way to solve this problem. As a matter of a fact you can annotate the dto directly, and the priority will be considered (as discussed previously), but this piece of advice might render useful in the future for other use cases. I see in your description that you use LightSpeed ORM. Any thoughts on ServiceStack.OrmLite vs LightSpeed for SQLServer 2008? I like ServiceStack's OrmLite because it is very easy to use, but at the same time, it fails to provide complex operation support. – Cristóvão Jan 22 '14 at 13:45
  • @Blitzkrieg I like ORMLite but sometimes I find it lacks some of the power you get from a bigger ORM, such as caching, proper joins, for complex queries and concurrency features. I use LightSpeed primarily because it can be used database provider agnostically, so I can swap out MSSQL for MySQL or Oracle (there are many more providers supported). It is commercial but as a result you get a solid product. I have been using since v2, it's now v5. The support is fantastic, and there is nightly builds, which means fast fixes. They do offer a free trial for small data models. – Scott Jan 22 '14 at 13:59
  • Makes sense. Do you publish/blog about the architectural design decisions (loosely speaking) you have taken for your platform? – Cristóvão Jan 22 '14 at 14:34
  • @Blitzkrieg Not at the moment, but I am currently working on blog articles which cover my use of ServiceStack. I hope to have them ready in about 3 weeks. – Scott Jan 22 '14 at 14:39
  • What's your take on error formatting? I have my mind up about how I want my service to output stuff. And indeed, I have my services behaving accordingly. However I am not sure my way is the best possible. I have my validators returning both the Http Status Code, and the error JSON body wrapped inside an HttpResult. Also, I have a custom service runner where I catch those unchecked exceptions, logging them and throwing a 500 error. In general what's your take on this? Maybe i can produce a question here on SO with a detailed description and you can give ur insight? – Cristóvão Jan 22 '14 at 22:58