Automatic Filtering of accessible rows by Users

In database-speak, this is known as row-based security or horizontal filtering. Oracle calls it Virtual Private Database. This feature will filter the instances of a model that can be accessed by a particular user, behind the scenes, ie she will not be shown the instances at all and will not be aware of their existence.

This should not be confused with column-based security (vertical filtering) which is implemented using database views. In Maymyo, you can implement this by creating specific views or admin programs that shows certain fields only and allowing access to these programs to certain users using our menus or access list (ie programs may be shown in a menu but access is controlled using either Access Level or Access List).

What does it mean?

To understand this, let’s look at an organisation using Maymyo which has Branches (which are also Business Units) located in several cities in a large country. Every User who can access Maymyo must belong to a Business Unit.

You want to control access to your transactional data by allowing Users to access data that belong to her Business Unit only and not other Business Units. eg a User in the Chicago office should not see any data of any other cities except her own.

Maymyo will implement this by defining a Field Restriction for the Business Unit field. This is a Foreign Key field that refers to our BusinessUnit model. Then for all transactional models that has this BusinessUnit field, we will define a Model Restriction with a Default Restriction Value of MY-BUSINESS-UNITS. Then any super-users who can see all data can have an entry in User Restrictions with the Restriction Value of ALL-ROWS. The Default Restriction Value will be used on all Users who are not defined in the User Restrictions.

In our FilterManager class, MY-BUSINESS-UNITS will return a list of accessible Business Units of a User. Remember that Business Units can be a hierarchy? If a User belongs to a parent Business Unit, then she can access all its children Business Units. This is what we mean by accessible Business Units.

How to use it for your own models

If you look at our ValueSet model definition in the Auditing topic, you will notice the following lines:

# Override Manager to our own FilterManager
objects = FilterManager()
# Need another Manager without filtering for use in basic system functions
all_objects = models.Manager()

This is right after all the field definitions of ValueSet and just before the inner Meta class. What this does is to override the normal model manager to our custom FilterManager. You will also need to create a reference to the normal manager using all_objects. There may be certain situations when we need to allow a user to access all instances, eg when displaying a list of choices for a ValueSet. But this is within the control of the programmer (ie you).

You will also need to import the FilterManager first, before you can use it.:

from infra.custom.filter_manager import FilterManager

This is the first step. Next you will need to define the filtering criteria. These are the following maintenance. You will find this in the “Maintain Infrastructure Master Files” menu under the “Application Security and Access Control” sub-tree’s “Maintain Field Restrictions”.

  1. Maintain a Field Restriction for the field
  2. Maintain a Model Restriction for models which has this field
  3. Maintain User Restrictions (one per user) for those allowed access to instances.

You must have a field in your model whose value is used to decide access to certain users. In our Business Unit example above, we have a field called business_unit in our common.Branch model.

Dynamic Filtering of accessible rows of Users
Maintain Field Restrictions

We define a unique “Restriction Code” called BUSINESS-UNIT and the “Field Name” is business_unit.

“Restriction Code” must be a unique field code in upper case and “Field Name” must be the same as your model’s field name. In the “Model Restrictions” tab, you will define which model (which has this field) we will apply automatic filtering to.


Maintain Model Restrictions

You will notice that you can define many Models per Field Restriction. This is because the same Field can exist in many Models, eg you may have a Branch field that exist in several transactional Models. You need only define a list of Users allowed access to specific field values and it shall be applied to all Models defined in Model Restrictions.

For each Model, you can define the “Default Restriction Value” for Users who are NOT in the “User Restrictions” (next tab). Besides using the actual value, there are certain special tokens you can use as Restriction Value.:-

  • ALL-ROWS - all instances of a Model can be accessed
  • NO-ROW - no instance can be accessed
  • MY-SUBORD - my (ie the user’s) subordinates only, ie used when the Field is a User Foreign Key field.
  • MY-SUBORD-BY-NAME - same as My Subordinates but when Field uses the username field.
  • MY-PEERS - my peers, ie same position in the Hierarchy.
  • MY-PEERS-BY-NAME - same as My Peers but using the username
  • MY-BUSINESS-UNITS - a User’s accessible Business Unit(s). A User must belong to a Business Unit and can have a list (comma delimited) of Business Units (by its code) that she can access. All these are her Accessible Business Units. If a Business Unit is a parent, then all its children (and grand-children) will be added to the list.

In addition, instead of using “Default Restriction Value”, you can use a Dynamic Filter, which returns a dictionary of filter predicate(s) which will be used against the Model. If you are familiar with Django’s ORM (Object Relational Mapper), you would have used its QuerySet filter() method which access predicates like “field__operator=value”. This is the predicate which is returned by a Dynamic Filter. You must create the Dynamic Filter yourself in “Maintain Dynamic Filters” in the “Application Executables” sub-tree in the same menu. The predicate returned must operate in your model’s field(s).

With Dynamic Filters, you can do virtually anything. We will always pass in the current User instance into your Dynamic Filter. It is up to you to define how to work out which rows the user can access by using the full power of QuerySet’s filter.


Application Hierarchies

The tokens above that applies to subordinates and peers make use of Maymyo’s Application Hierarchies. You can define a Hierarchy of Users in Maymyo. A Hierarchy is a pyramid of Job Positions, defining who reports to whom. For each Position, you may have Users who are holders of that position, starting from an effective date (until superseded by holding another position in the same Hierarchy).

A User may hold positions in 0, 1 or more Hierarchies. Hierarchies are used in Workflow document approvals and Reminder escalations.


Maintain User Restrictions

You should use the “Default Restriction Value” to define access to your Models for all Users and use the “User Restrictions” to define the exceptions. It is useful to allow super-users like “admin” to have access to all rows by using the ALL-ROWS token.