Doorgaan naar hoofdcontent

Your own security annotation

So, I'm on a small project which has some multi-tenancy. Simply put; user A can see the bananas on his trees, and user B can see the bananas on his own trees... but they can't see each others.
But it's restfull, so ideally, you'd call something like /tree/{tree-id}/bananas
And since we know who's executing the call (since it's authenticated), we can verify that it's user A calling us, and then check which trees he can see.
If he's trying to be sneaky, and does a restcall with a treeId of B, a security violation should occur.

Okay, so how do we do that?
Well, the application has a controller for that, and we'd want to secure it there.
So, assume we have the following code:
public List<Banana> getBananasOfTree(String tree)
Since it's supposed to be annotated, we'd use something like this:
@RequestMapping("/tree/{tree-id}/bananas")
@Secured // or some other requirement 
public List<Banana> getBananasOfTree(@PathVariable("tree-id") String tree)

In spring, we have several options. The first one I tried was a HandlerInterceptor.
It's an interceptor which executes just before the method is called, in spring's handlerchain. We can make it intercept on specific annotations, so... we just add an annotation, and we're groovy.
Well, almost. The annotation needs to be  on the method, so we can't put the annotation on the pathvariable. That's okay, we can reference it, right? Well, not quite. We can't bind by name, since the parametername is not available at runtime, it's stripped away (the signature of the method is (String arg0, User arg1)) That's annoying, so we'd have to do it either by type, or by parameter index, so our (method)annotation should have an index signifying which element we'd want to check. It's getting uglier by the second.

And sadly, that's not all. The HandlerInterceptor is executed before the actual parameter resolution. Since it's a webrequest, the actual binding of the tree-id parameter hasn't occured yet. And it's not something which is easily achieved in Spring to (redo) it's binding to get the actual value.
#sosad.

So, we'll have to resort to plain AOP.
How? Well, we define a pointcut, like this:execution(public * * (.., @MyAnnotation (*), ..))
(in other words, on the execution of a public method which contains MyAnnotation on a parameter)

That allows us both to have an annotation to trigger the aspect, and we can use that same annotation to signify the parameter which should be checked.

Great!
Of course, it's not done yet.
What happens in the aspect is a lot of reflection... The JoinPoint signifies the method, from the method we can retrieve the actual method (i.e. public List<Banana> getBananaOfTrees(...)) and then we still need to figure out which argument we want to have, so we loop over all parameters, find the first one having our nice annotation, save it's parameterindex, and from the actual arguments which we retrieve from the JoinPoint, we get the argument which contains the tree-id. And then, only then, can we verify if the currently logged in user (which can be retrieved through the securitycontext) is allowed to see the tree in question. *sigh*

Of course, the securitycontext doesn't hold the actual user... it only holds the name or principal. So we'll have to do a database lookup for it.

Yeah, that's kinda sucky. So... what do we need from the user? Well, we need his tree-id. That could be put on the security context easily. So once we do that, we'll have a 'moderately' clean solution.

And there was much rejoicing...

Reacties

  1. Also, with all that reflection jazz going on, on *every* call to that tree method... it may become slow. Something to consider...

    BeantwoordenVerwijderen

Een reactie posten

Populaire posts van deze blog

Spring's conditional annotation with properties

Spring has a nice @Conditional annotation, to have the option to have beans be available in the context depending a specific condition (Of course, this can also be realized by using @Configuration objects, but that's a different post). Ideally, we'd have the option to have a condition evaluate to true or false depending on a property. Sadly, Spring does not support that out of the box. Googling and looking around gives a partial solution, but the complete one here, so we won't forget: /** * Components annotated with ConditionalOnProperty will be registered in the spring context depending on the value of a * property defined in the propertiesBeanName properties Bean. */ @Target({ ElementType.TYPE, ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { /** * The name of the property. If not found, it will evaluate to false. */ String value(); /** * if the p...

OSGI insights without sonar

So I was on a project without sonar. Oh my. Well, it was an OSGI project, so the problems couldn't be that bad, right? But how good were they (and what things were bad?) I found Stan4j , a code analysis tool for eclipse, which draws nice graphs and can handle osgi pretty well it seems. Now I can see that dependencies/bundle names aren't properly aligned (even though OSGI doesn't complain), etc.

JPA and transactions

So I was working with JPA and transactions. Consider the following: In bean 1, with implicit TX 1, managed entities are loaded/created,and returned in bean 2, with implicit TX 2, entities are modified in bean 3, with NO TX, bean 1 is called, and the results are passed to bean 2. and bean 4 is similar to bean 3, but with it's own transaction, TX3 What happens when bean 3 finishes: are the entities updated? What happens when bean 4 finishes, are the entities updated? The answer to this is simple; entities are managed through a persistance context. That context is tied to the transaction. So in bean 2, there is a difference. When called from bean 3, it runs in a different transaction then bean 1, and thus a different persistance context, and thus the entities are not managed 'by this transaction'.When called from bean 4, it all runs in the same transaction, TX3, and the results are persisted.