UgandaEMR: Use cases for the Data Filter module

When you say “didn’t quite work”, could you spell that out a bit more? I’ve used it successfully in the past…

@aojwang From my draft i am basing this logic around the initializer

Basically;

  • I have a role called Mosul: Sensitive Appointments that has a privilege, Manage Sensitive Appointments
  • I have an Appointment Service definition Mental Health that i map to an appointment Service type Mental Health Service Type

Inside my datafiltermappings/mappings.csv file i grant access to the Privilege on the basis of the Appointment Service Type

i had not seen Bypassing a single filter. Seen it now. Thanks

I added the below global properties but we couldn’t pass even the login page on o3. Unless I needed to do anything extra to have the filters disabled. I can give it another try and share feedback.

	<globalProperty>
		<property>datafilter_locationBasedUserFilter.disabled</property>
		<defaultValue>true</defaultValue>
		<description>
			Location based user filter. To enable, set value to false
		</description>
	</globalProperty>

	<globalProperty>
		<property>datafilter_locationBasedPatientFilter.disabled</property>
		<defaultValue>true</defaultValue>
		<description>
			Location based patient filter. To enable, set value to false
		</description>
	</globalProperty>

Oh! There are way more than just those two filters in DataFilter by default and you likely need to disable all of them… (Basically, everything listed in any JSON file here; at a glance you were still hitting the datafilter_locationFilter, which filters locations to those a user is assigned; if user’s haven’t been assigned a location, then no locations would appear).

I see. Thanks, @ibacher. I will try it out again and share feedback.

@aojwang @ibacher @dkayiwa How can i load a filter from a .json file using a custom module

i have a filter on appointments that i believe should check for the corresponding privilege. I want to load this via a custom module, but however, also testing this with the datafilter module throws an error parameter roles is not specified

[
    {
        "name" : "datafilter_encTypePrivBasedAppointmentUserFilter",
        "targetClasses" : [
            "org.openmrs.module.appointments.model.Appointment"
        ],
        "condition" : "patient_appointment_id IN (SELECT DISTINCT pa.patient_appointment_id FROM patient_appointment pa INNER JOIN appointment_service apts ON pa.appointment_service_id = apts.appointment_service_id WHERE apts.name = 'Mental Health' AND 'Manage Sensitive Appointments' IN (SELECT rp.privilege FROM role_privilege rp WHERE rp.role IN (:roles)))",
        "parameters" : [
            {
                "name" : "roles",
                "type" : "string"
            }
        ]
    }
]

cc @michaelbontyes @pirupius

@wyclif will tell but I wonder if your custom module doesn’t need to provide its override of DataFilterListener in order for supports(String) to return true for your implementation-specific filters?

The default implementation will only be truthy for the filters that are baked in (here).

If I’m right then typically this area of the code should be enhanced to allow Data Filter to be configurable. Would be great if your group could help do that through a next major!

At the very least it would be great if your group could document in the wiki the process, under the current architecture of the module, to bring in own filters through a custom module → https://openmrs.atlassian.net/wiki/spaces/docs/pages/25476994/Data+Filter

1 Like

Thank you @mksd

I have decided to extend org.openmrs.module.patientgrid.filter.DataFilterBeanFactoryPostProcessor like bellow:

@Component
public class CustomDataFilterBeanFactoryPostProcessor extends DataFilterBeanFactoryPostProcessor {
    @Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        super.postProcessBeanFactory(beanFactory);
    }
}

but it seams hibernate files are not loading correctly from my resource files so i get java.lang.IllegalArgumentException: InputStream cannot be null error caused from this point

See full log here

However, i have the add-entity-filter-xslt-template.xml and update-mapping-loc-xslt-template.xm

├── src
   ├──── resources
           ├──── filter
                  ├────hibername
                  └── appointment_privilege.json
           └── add-entity-filter-xslt-template.xml
           └── update-mapping-loc-xslt-template.xm

Could you know what I am missing?

I don’t think you should go down the invasive route of creating a DataFilterBeanFactoryPostProcessor.

Instead, like @mksd mentioned, you have to register a spring bean that implements DataFilterListener, it has the onEnableFilter callback method where you’re expected to set values for any parameters defined in your filter’s definition file, so you’re supposed to set the roles parameter in your listener.

Datafilter comes with a single implementation for its core filters that you can find here where you can borrow some reusable logic, @mksd in the future we might want to add a base class for this listener that comes with some of this reusable logic that concrete classes can extend.

2 Likes

You don’t need to add xslt files, filter definition files are loaded from the classpath, just make sure that in your custom module, you place your filter definition files in src/main/resources/filters/hibernate, if your persistent classes are indexed using hibernate search then you also need to add filters for them in src/main/resources/filters/fulltext along with another DataFilterListener implementation and impl class for full text queries to set any parameters, you can find examples in the datafilter module.

We definitely need to document this process.

1 Like

:heavy_plus_sign: 1

@jnsereko can you guys take care of doing so in the wiki when all the steps have been identified?

1 Like

Am willing to help after running it successfully in my custom module.

I thought adding the following to my custom module and having /src/resources/filters/hibernate/appointment_privilege.json had to do it @wyclif It still seams that the filter is not loaded

@Component
public class MosulAppointmentsDataFilterListener implements DataFilterListener {

    @Override
    public boolean onEnableFilter(DataFilterContext filterContext) {
        if (filterContext.getFilterName().startsWith("datafilter_encTypePrivBasedAppointment")) {
			Collection<String> roles = new HashSet<>();
			if (Context.isAuthenticated()) {
				Collection<String> allRoles = Context.getAuthenticatedUser().getAllRoles().stream().map(Role::getName)
				        .collect(Collectors.toSet());
				roles.addAll(allRoles);
			}
			
			filterContext.setParameter(ImplConstants.PARAM_NAME_ROLES, roles);
			
		}
        return false;
    }

    @Override
    public boolean supports(String filterName) {
		return true;
    }
}

Note that i was able to successfully load it in the datafilter module after adding my filter name to the baked in (here) following @mksd’s suggestion.

Can you link to the code you tried this in? You shouldn’t need to modify anything in the data filter module itself. (I would also not recommend writing supports() such that it just returns true; by doing that you are opting-in to handling all filter. Instead, your supports() function should:

return filterName.startsWith("datafilter_encTypePrivBasedAppointment")

As long as your DataFilterListener is a Spring bean, your filters are in src/main/resources/filters/hibernate or src/main/resources/filters/fulltext and your module is configured to depend on the data filter module, everything else should be automatically picked up.

Also, if you’re going to use an OpenMRS security primitive to control access to something, please use privileges instead of roles. This will make maintenance easier (if you hard-code roles, then if a new role needs access to sensitive appointments, you need to modify your module, rebuild and ship it; if you use privileges, you can just assign the privilege to a role, no code changes required).

3 Likes

The location I mentioned is src/main/resources/filters/hibernate (PLEASE READ CAREFULLY :grinning:), do you see the difference?

3 Likes

+1 to @ibacher’s comment about how to implement supports().

@ibacher for the core listener and filters we intentionally used roles and not privileges and I think @jnsereko tried to follow the same approach, we wanted avoid a very long IN clause for the privileges in the query since the privilege graph in OpenMRS can get wild as compared to the roles graph, maybe it’s not a big deal.

@jnsereko I would recommend that you borrow some checks in the core DataFilterListener, you probably don’t want to apply filters for Daemon and Super user.

2 Likes

@aojwang @ibacher, could you know why a filter like datafilter_encTypePrivBasedAppointmentUserFilter shown above or the one shown below prevents a non-admin user from searching a patient but can access the details in chart? Refer to attachments below

Filter

[
    {
        "name" : "datafilter_appointmentPrivBasedAppointmentServiceUserFilter",
        "targetClasses" : [
            "org.openmrs.module.appointments.model.Appointment"
        ],
        "condition" : "patient_appointment_id IN (
            SELECT pa.patient_appointment_id FROM patient_appointment pa 
            INNER JOIN appointment_service apts ON pa.appointment_service_id = apts.appointment_service_id 
            WHERE 'Manage Sensitive Appointments' IN (:privileges) OR apts.name NOT LIKE '%Mental Health%')",
        "parameters" : [
            {
                "name" : "privileges",
                "type" : "string"
            }
        ]
    }
]

@ibacher, i am sharing the code in a few

@jnsereko I see the filter is named datafilter_appointmentPrivBasedAppointmentServiceUserFilter I would expect filters provided by other modules not to have a datafilter prefix, it should be your module Id instead. This name seems not intuitive to me too, it suggests that it filters users when actually it filters appointments.

3 Likes

@wyclif I renamed it to patientidentifiergenerator_appointmentServicePrivBasedAppointmentFilter but still i am not able to search for patients on non-admin users.
Could you know any other possible cause?

Renaming the filter was just a by the way, it was not meant to fix why you can’t search for patients.

As to why you’re unable to search for patients for non admin users, I guess it may be caused by other filters, please make sure all are disabled.

1 Like