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 typeMental 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.
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"
}
]
}
]
@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
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.
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
@jnsereko can you guys take care of doing so in the wiki when all the steps have been identified?
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).
The location I mentioned is src/main/resources/filters/hibernate
(PLEASE READ CAREFULLY ), do you see the difference?
+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.
@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.
@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.