...
...
...
...
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?
...
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 locationthe location to which the data is associated. In this case
- the basis is
Location
, - and the entity is
User
.
...
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".
...
Code Block | ||||
---|---|---|---|---|
| ||||
{ "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)", "parameters": [ { "name": "attributeTypeId", "type": "integer" }, { "name": "basisIds", "type": "string" } ] } |
The condition reads "include the data visit for patients that have a specific "location" attribute type whose value is either of the bases (=locations)". Of course almost identical filters are defined for patients, encounters and observations.
The logic here is that every piece of data (whether visit, encounter, observation or patient itself) link refers to a patiend_id
that is subject to the above filter condition.
This leads to a last question: how do we know which person attribute contains the value of the location to which a patient pertains?
There is an in-built mechanism in Data Filter that helps configure such implementation-specific linkages between data and filtering bases. A global property "datafilter.personAttributeTypeUuids"
exists to contain a list of UUIDs of person attribute types. Each of the person attribute types in that list is assumed to have a different format. For instance only one of the person attribute types listed in the global property can be of the format Location
. In the filter definition above this means that attributeTypeId
is of a type whose format is Location
because that is the type of the bases. And in the context of location-based filtering the bases type is Location
.
See here for how this specific logic is implemented for the location-based filters.