Using decorators for security

Software is filled with cross-cutting concerns, aspects that need to be implemented consistently, even if they're in separate class hierarchies. It's often a mistake to try and impose a class hierarchy around a cross-cutting concern. We've looked at a few examples, such as logging and auditing.

We can't reasonably demand that every class that might need to write to the log also be a subclass of some single Loggable superclass. It's much easier to design a Loggable mixin or a @loggable decorator. These don't interfere with the proper inheritance hierarchy that we need to design to make polymorphism work correctly.

Some important cross-cutting concerns revolve around security. Within a web application, there are two aspects to the security question as follows:

  • Authentication: Do we know who's making the request?
  • Authorization: Is the authenticated user permitted to make the request?

Some web frameworks allow us to decorate our request handlers with security requirements. The Django framework, for example, has a number of decorators that allow us to specify security requirements for a view function or a view class.

Some of these decorators are as follows:

  • user_passes_test: This is a low-level decorator that's very generalized and is used to build the other two decorators. It requires a test function; the logged-in User object associated with the request must pass the given function. If the User instance is not able to pass the given test, they're redirected to a login page so that the person can provide the credentials required to make the request.
  • login_required: This decorator is based on user_passes_test. It confirms that the logged-in user is authenticated. This kind of decorator is used on web requests that apply to all people accessing the site. Requests, such as changing a password or logging out, shouldn't require any more specific permissions.
  • permission_required: This decorator works with Django's internally defined database permission scheme. It confirms that the logged-in user (or the user's group) is associated with the given permission. This kind of decorator is used on web requests where specific administrative permissions are required to make the request.

Other packages and frameworks also have ways to express this cross-cutting aspect of web applications. In many cases, a web application may have even more stringent security considerations. We might have a web application where user features are selectively unlocked based on contract terms and conditions. Perhaps, additional fees will unlock a feature. We might have to design a test like the following:

def user_has_feature(feature_name): 
    def has_feature(user): 
        return feature_name in (f.name for f in user.feature_set()) 
    return user_passes_test(has_feature) 

This decorator customizes a version of the Django user_passes_test() decorator by binding in a specific feature test. The has_feature() function checks a feature_set() value of each User object. This is not built-in in Django. The feature_set() method would be an extension, added onto the Django User class definition. The idea is for an application to extend the Django definitions to define additional features.

The has_feature() function checks to see whether the named feature is associated with the feature_set() results for the current User instance. We've used our has_feature() function with Django's user_passes_test decorator to create a new decorator that can be applied to the relevant view functions.

We can then create a view function as follows:

@user_has_feature('special_bonus') 
def bonus_view(request): 
    pass 

This ensures that the security concerns will be applied consistently across a number of view functions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset