Skip to content
This repository has been archived by the owner on Apr 29, 2021. It is now read-only.

DSU Reference Implementation: Getting Started

John Jenkins edited this page Oct 14, 2013 · 18 revisions

This guide is designed to instruct someone on getting the source code and associated tools, getting the web application running, and basic authentication/authorization and I/O.

First, let's walk through what needs to be done to setup the environment.

  • MongoDB - the defaults will work, but they can be overridden via the configuration file.
  • A servlet container - Internally, we test with and use Tomcat.
  • Git - To get the source and stay up-to-date with changes.
  • Ant - To build the source.

Begin by ensuring that the database is running. Then, connect to the database's shell and, specifically, to the Open mHealth database. For MongoDB, this can be executed by running the following commands from the command line:

mongod >/tmp/mongod.status &
mongo
use omh

Now, create a new schema in the registry with an ID "omh:example" and version 1. The "schema" must follow the Concordia specification. For MongoDB, this should look something like:

db.registry.insert({"schema_id":"omh:example","schema_version":1,"schema":{"type":"object","fields":[]}});

Any Servlet container should do; we tested internally using Tomcat. There is no additional setup required.

  1. Checkout the source from this repository and drill down into the "dsu" directory in that source.
  2. Run "ant clean dist", which will build a WAR file and put it in the "dist" directory.
  3. Copy the WAR file and put it in the servlet container's web applications directory.

Note: It is important that you not copy or start the Open mHealth web application until the database is running. The web application attempts to connect to the database on startup, so starting the web application prematurely may cause errors. If so, a simple restart of the web application (or servlet container) will fix it.

A default configuration file is provided and used; it is located at web/WEB-INF/config/default.conf. This configuration file should be used as a template for a custom one, but it is rare that this file would ever need to be edited. It should never be removed.

To override any of the settings in this file, an external configuration file will be searched for at the following locations:

Windows %PROGRAMDATA%\OpenmHealth\config\omh.conf

POSIX /etc/omh.conf

Now that everything is setup, let's run through a series of commands to add and view data.

Users can be created by either having their information directly inserted in the database, of course, or by self-registering. When self-registering, the user will need to provide a username, password, and email address. The system will then send an email to that user with a link, which, when clicked, will activate the user's account.

curl -v -d "username=Test.User" -d "password=Test.Password0" -d "email=<YOUR_EMAIL>" http://localhost:8080/omh/v1/users/registration

To disable this feature, comment out the @RequestMapping annotation for the registerUser(String, String, HttpServletRequest, HttpServletResponse) and activateUser(String, HttpServletRequest, HttpServletResponse) in the Version1 class. You can comment out the entire function, but, by doing only the @RequestMapping annotation, Spring will fail to connect the functionality to the API, which will result in the same effect.

The email will attempt to use SSL. If the machine that is running this web application does not have access to a SSL certificate for its domain, this can be disabled by removing the "MAIL_PROPERTY_SSL_ENABLED" property from the "MAIL_SESSION_PROPERTIES" in the UserRegistrationRequest class. This can be found near the top of the class in a static block with the other class constants.

The registry should be able to be read from the following URLs (potentially, with minor tweaks for your environment):

http://localhost:8080/omh/v1
http://localhost:8080/omh/v1/omh:example
http://localhost:8080/omh/v1/omh:example/1

This can be achieved with a simple cURL command:

curl -d "username=Test.User" -d "password=Test.Password0" http://localhost:8080/omh/v1/auth

To upload data, a POST must be made to the "data" URI of some schema, e.g.:

curl -d "omh_auth_token=<TOKEN>" -d "data=[{\"data\":{}},{\"data\":{\"extraField\":\"foo\"}}]" http://localhost:8080/omh/v1/omh:example/1/data

The data should be able to be read from the following URLs (potentially, with minor tweaks for your environment):

http://localhost:8080/omh/v1/omh:example/1/data?omh_auth_token=<TOKEN>

Users may grant others access to their data; these "others" are called "third-parties". In OAuth, these are known as clients. One user must register a third-party, which will return to them the ID and shared secret for that third-party. This can be done with the following cURL command.

curl -d "omh_auth_token=<TOKEN>" -d "name=My Third Party" -d "description=A test third-party (client) to be used to read data from other users." -d "redirectUri=http://localhost:60000/redirect" http://localhost:8080/omh/v1/auth/oauth/registration

This will return a client ID (<CLIENT_ID>) and secret (<CLIENT_SECRET>), which will be used below.

Now, we need to create a new user, have them upload some data, and then have them share that data with your newly-created third-party.

Setting up the Scenario

First, we are going to create a second schema to show how some data is shared and other isn't:

db.registry.insert({"schema_id":"omh:example:other","schema_version":1,"schema":{"type":"object","fields":[]}});

Next, create a new user, and give them the username "Other.User" and the same password.

Now, get a token for that user:

curl -d "username=Other.User" -d "password=Test.Password0" http://localhost:8080/omh/v1/auth

And, upload some data to both schemas:

curl -d "omh_auth_token=<TOKEN>" -d "data=[{\"data\":{\"originalSchema\":\"foo\"}}]" http://localhost:8080/omh/v1/omh:example/1/data
curl -d "omh_auth_token=<TOKEN>" -d "data=[{\"data\":{\"otherSchema\":\"foo\"}}]" http://localhost:8080/omh/v1/omh:example:other/1/data

Create the Authorization Request

Now, we need to formulate a request for the other user to grant us access only to the "omh:example:other" schema:

curl -v -d "response_type=code" -d "client_id=<CLIENT_ID>" -d "scope=omh:example:other" http://localhost:8080/omh/v1/auth/oauth/authorize

This will respond with a HTTP 302 status code, which, in a normal OAuth flow, is when the user gets redirected to the remote service to authorize the third-party (client). In our case, we are going to do it all through the command-line. There is a very basic authorization page packaged with the reference implementation, but it was experiencing some issues at the time of writing this.

From the response, all you will need is the code from the redirect URL, which will be used in several steps below.

Authorize the Third-Party

Now, we will authorize the third-party as the other user:

curl -v -d "username=Other.User" -d "password=Test.Password0" -d "granted=true" -d "code=<CODE>" http://localhost:8080/omh/v1/auth/oauth/authorization

The response will be another redirect, which is the redirect URL that was given when the third-party was registered. If you followed this example, it will probably be a dead link.

Exchange the Authorization Code for an Authorization Token

The authorization token is what should be used to request data on behalf of the user. It includes a refresh token which can be used to create a new authorization token before or after the original has expired. It can be obtained from the following cURL command:

curl -d "grant_type=authorization_code" -d "redirect_uri=http://localhost/" -d "code=<CODE>" -d "client_id=<CLIENT_ID>" -d "client_secret=<CLIENT_SECRET>" http://localhost:8080/omh/v1/auth/oauth/token

Note: The "redirect_uri" parameter is unused. This is a deficiency of an existing library and will not be used.

Request Data on Behalf of the User

To request data, the requesting user must provide an authentication token to identify who is making the request as well as an authorization token to prove that the requesting user has access to the other user's data. The authentication token is provided as a parameter, the same as with a normal data read call. The authorization header is provided as an Authorization HTTP header. Now that the user has granted the third-party access to their data, the following cURL command can be used to retrieve the data:

curl -H "Authorization: Bearer <AUTHORIZATION_TOKEN>" http://localhost:8080/omh/v1/omh:example:other/1/data?omh_auth_token=<AUTHENTICATION_TOKEN>\&owner=Other.User

Requesting data from the original schema will result in an error because the authorization token does not provide access to that data.

curl -H "Authorization: Bearer <AUTHORIZATION_TOKEN>" http://localhost:8080/omh/v1/omh:example/1/data?omh_auth_token=<AUTHENTICATION_TOKEN>&owner=Other.User

Refresh an Authorization Token

Once a token has expired, it can be exchanged for a new token via the refresh token:

curl -d "grant_type=refresh_token" -d "refresh_token=<REFRESH_TOKEN>" -d "client_id=<CLIENT_ID>" -d "client_secret=<CLIENT_SECRET>" http://localhost:8080/omh/v1/auth/oauth/token

Once you have a firm handle on how the reference implementation is working, let's drill down into how to change it. This implementation follows the Model-View-Controller (MVC) paradigm with the "filter" and "servlet" packages acting as the view layer, the "request" package acting as the controller layer, and the "data" and "domain" packages acting as the model layer. The other packages are primarily utility packages that are used throughout the application to do utility-based operations like setup and teardown the logging and database connections, decorate Concordia, define exceptions, etc..

The view layer is the layer that receives HTTP requests and responds with HTTP status codes and headers and, generally, JSON content.

The ExceptionFilter immediately passes the request along but surrounds it with a try-catch block. All known exceptions will propagate here, where an appropriate status code and body can be attached to the response before closing the connection. If auditing of requests were desired, this should probably be added just before this filter in the web.xml file in order to guarantee that the audit received the status code and response.

The AuthFilter is used to retrieve any authentication and/or authorization information from the request and store it inside the HttpServletRequest object as it is passed through the other filters and onto the Servlet handler.

SpringSource's Web-MVC package takes over from here to connect the request to its appropriate Servlet class and method within that class. Conversely, once the request has been fully processed, assuming an exception was not thrown, it will set the status code as successful, serialize the object (if any), and return it.

The view layer will construct a Request object, call service() on it, and return whatever is set as the request's data. The request objects handle specific requests or workflows. All requests must be subclasses of Request or ListRequest which indicate the type of response that should be generated by that request.

The model layer is responsible for modeling all concrete objects in the system, e.g. authentication tokens, schema definitions, data, etc.. The "domain" package handles concrete objects like tokens, schemas, and data; the "data" package handles collections and their storage, e.g. the registry, the authentication token bin, the data bin.

All classes should be adequately documented. A build target of "javadoc" will create JavaDoc for all of the classes and place the result in a "doc" sub-folder of the DSU's "dist" folder.

The reference implementation comes with an implementation for MongoDB and MySQL databases. These implementations can be found as sub-packages to the org.openmhealth.reference.data and org.openmhealth.reference.domain packages. To tell the system to use the MySQL implementation instead of the MongoDB implementation, you will either need to create your own configuration file (recommended) or modify the default one located at web/WEB-INF/config/default.conf. The custom configuration file should either be at /etc/omh.conf (POSIX systems) or %PROGRAMDATA%\OpenmHealth\config\omh.conf (Windows). In either case, set the db.class property to org.openmhealth.reference.data.sql.mysql.MySqlDao.

One of the major goals of the reference implementation was to make it possible and straightforward to add a custom backend. Using the three existing backends for reference is probably the best way to explore how to do it. The goal is to implement a custom implementation of the root org.openmhealth.reference.data classes. The custom Dao class should initialize the other classes and setting that custom DAO class as the db.class in the configuration file will cause all of the DAO classes to be initialized. The root org.openmhealth.reference.domain classes can be used directly, without being subclassed, but, as is the case with the MongoDB implementation, it is perfectly acceptable to have custom implementations that take into account additional database information that is not handled by the default implementations.

The interesting part, and the motivation for this design, is that there is no reason that the database actually even be a database. These classes could pull from anywhere including local or remote web services. Therefore, the reference implementation could be a drop-in Open mHealth implementation for an existing web service. Going further, there is no reason that this be an all-or-nothing approach. A custom DAO class could use the MongoRegistry class to maintain the list of schemas, a web service to pull the data, and a custom LDAP implementation for the authentication/authorization services.