CloudServices/Sagrada/ServerDevGuide

From MozillaWiki
Jump to: navigation, search

Developing a Sagrada Service

Sagrada Services are implemented as Pyramid web applications, using some additional libraries such as cornice and mozsvc to make common tasks easier.


Application Structure

The demoapp project shows the basic structure of a Sagrada service application. The top-level module should expose a function named "main" which will build the pyramid application configuration and return the corresponding WSGI application::

   from mozsvc.config import get_configurator
   
   def main(global_config, **settings):
       # Load configuration from the .ini settings file.
       config = get_configurator(global_config, **settings)
       
       # Use cornice for easily building the web-api.
       config.include("cornice")
       
       # Add various Mozilla default views and helpers.
       config.include("mozsvc")
        
       # Use standard authentication modules
       config.include("mozsvc.user")
       
       # Pull in view definitions from the app itself.
       config.scan("demoapp.views")
       
       # Turn it into a WSGI application object.
       return config.make_wsgi_app()

Consuming Authentication Tokens

As described in Services/Sagrada/TokenServer, all access to Sagrada services is authenticated using MAC Auth tokens. This section describes how to process these tokens and extract the relevant information.

Using the mozsvc helpers

By including the "mozsvc.user" package, applications are able to transparently consume Sagrada authentication tokens. To obtain the authenticated userid in your view code, simply access the "user" attribute of the request like so:

   def some_view(request):
       uid = request.user.get("uid")
       if uid is None:
           return "Not Authenticated"
       else:
           return "Authenticated as " + str(uid)

Requests with a valid authentication header will have request.user as a dict containing the userid and other metadata, while requests without an authentication header will have request.user as an empty dict. If the request has an invalid authentication header then accesing request.user will result in a 401 response being returned to the client.

Behind the scenes, mozsvc configures an instance of the pyramid macauth plugin and installs it into the pyramid authentication machinery. The deployment configuration file must point to the location of the node secrets file:

   [macauth]
   secrets_file = /path/to/secrets/file


Using lower-level libraries

For applications with more complex needs, it may be necessary to perform explicit validation of MAC Auth signatures. This can be achieved by using the following lower-level libraries.

The macauthlib library provides functions to construct and validate MAC Auth signatures on a request, in particular:

  • macauthlib.get_id(request): get the MAC Auth id claimed in the Authorization header of the given request.
  • macauthlib.check_signature(request, key): verify the MAC Auth signature in the Authorization header of the given request.


The tokenlib library provides functions for parsing and validating authentication tokens, in particular:

  • tokenlib.parse_token(token, secret): check that the given token was signed with the given secret, and extract the embedded dict of authentication data.
  • tokenlib.get_token_secret(token, secret): get the secert key associated with the given authentication token.

To validate a MAC Access Authentication header, the server must perform the following steps:

  1. Extract the MAC Auth id from the header
  2. Obtain the corresponding MAC Auth key
  3. Validate the MAC Auth signature:
    • Check that the timestamp is not too far in the past
    • Check that the nonce has not been re-used
    • Calculate the expected value of the MAC Auth signature
    • Check whether it matches the value included in the request
    • Cache the nonce to prevent re-use
  4. Extract the user metadata from the MAC Auth id

The following minimal code performs these steps:

   # Get the MAC Auth id from the header.
   id = macauthlib.get_id(request)
   
   # Determine the corresponding MAC Auth key
   key = tokenlib.get_token_secret(id, node_master_secret)
   
   # Check the MAC Auth signature.
   if not macauthlib.check_signature(request, key):
       raise HTTPUnauthorized("Invalid Signature")
   
   # Parse the token to obtain user data.
   data = tokenlib.parse_token(id, node_master_secret)
   return data["uid"]

The node master secret must be obtained from the deployment configuration, e.g. from a setting in the config file or read from a shared node secrets file.