Background
This module provides a mechanism for filtering persistent data on a configurable and customisable basis. In a nutshell it enforces metadata-based access to users and/or roles.
For instance, out of the box, the module supports location-based and privilege-based access control to patient records and their clinical data. The initial implementation for those two use cases is restricted to the patients and their visits, encounters and observations.
Architectural Overview
The data model has been kept simple deliberately such that only two concepts matter: 1) which entity is being restricted by the filtering and 2) on what basis does this filtering restriction operate?
- The basis is the metadata on which the data access is based.
Eg.Program
for program-based access,Location
for location-based access, ... etc. - The entity is what is authorised to access the data.
Eg.Role
orUser
.
This is all controlled and stored in a single table modelled by EntityBasisMap
and can be configured through DataFilterService
.
Examples
Let us go through a couple of filters that are baked into the module. This will help highlight how much of the design of filters follows systematic rules, and how much of it is implementation-specific.
1. Location-based access
When this filter is enabled, users are prevented access to data based on their login location. In this case
- the basis is
Location
, - and the entity is
User
.
This raises a first question: how do we determine which locations a user has access to?
This is handled natively through the EntityBasisMap
, users are mapped with their basis through this built-in table of Data Filter.
Of course every user can be mapped to multiple locations, it does not have to be just one. Those will be the only locations that user will get to see, and log into, those will be the user bases.
Then a second question: how do we associate the data to a location so that the filter knows, when it is looking at some data, to which location it pertains?
This type of assumption is implementation-specific and in the case of the location-based filter that is bundled with the module, the assumption is that this is made at the patient level. There is therefore a need to associate a patient with a location, and this is done through a specific person attribute type. This person attribute type points to a location that marks the "patient's location".
Best is to look at one of the filter's conditions, see here:
{ "name": "datafilter_locationBasedVisitFilter", "targetClass": "org.openmrs.Visit", "condition": "patient_id IN (SELECT DISTINCT pa.person_id FROM person_attribute pa WHERE pa.person_attribute_type_id = :attributeTypeId AND pa.value IN (:basisIds) AND pa.voided = 0)", ... }
This reads "selects only patients that have a specific person attribute whose value is either of the bases", where those bases are the locations that the user has access to.
Another question remains: how does Data Filter know which person attribute type is used to link patients to their location?
TBC.
There are a couple of end-to-end tests in place showing the module's mechanics, see for instance PatientFilterTest
.
Roadmap
We envision adding filtering of other patient clinical data like orders, encounter diagnoses, appointments, ...etc.
We also envision a scenario where another module can leverage this module's functionality to limit access by some other criteria e.g. role-based access.
With that said, we hope to strip the location-based and program-based functionality out of this module leaving it as a very thin lightweight and reusable module purely for data filtering. The location-based access filtering should be moved to the existing Location based access module for instance.
Developer Guide
User Guide
Download
Installation
NOTE
To guarantee proper functionality of OpenMRS, it's highly recommended to install the module by stopping OpenMRS, drop the .omod file in the module repository and start OpenMRS again, this also applies to uninstall it, you need to stop OpenMRS, remove the .omod from the local module repository and start OpenMRS.
Configuration
- Set the value for the global property named datafilter.personAttributeTypeUuids, it should be a comma separated list of uuids for the person attribute types to use as the basis for filtering,
The format property of the matching person attribute type(s) MUST match valid java class names and should match values in the basis_type column in the datafilter_user_basis_map table. E.g. if you want to add location based filtering, you would filter by Location meaning the format of the person attribute type with the uuid to set for the global property would be org.openmrs.Location. - Every patient in the database should be assigned a person attribute type that ties them to a location, the attribute type should be the one that was set in above.
- Assign users to a location by adding entries for them in the datafilter_user_basis_map table, the user_id column is the database id of the user, the basis_id column is the id of the Object to filter on e.g. a location Id in case you are filtering by location. The basis_type column value should be a fully qualified java class name of the objects to filter on, e.g. in case of location based filtering it would org.openmrs.Location.