Architecture > Authentication
Data Filter
As part of AMI's custom Java plugin functionality, data filters are highly customizable Java plugins that can be used to set user permissions for data access in AMI applications.
Overview
The data filter plugin controls what a given user can access based on some parameters, e.g; region. This stops users from accessing data they are not entitled to.
Generally, the web filter is deployed one of two ways:
- Filtering real-time data as rows are updated.
- Filtering the resulting table of a datamodel query.
Typically, a data filter is invoked after some internal authentication system which will assign system variables to a user, such as region. The instructions for writing a custom entitlements plugin can be found here.
Note
A data filter is initialized per session of AMI -- if multiple users are using the same AMI dashboard, each user will have a separate instance of the data filter applied to them.
Requirements
You will need to follow the general steps for setting up a custom plugin. Once your Java project has been set up, ensuring autocode.jar
and out.jar
are included in your build path; you will need to include the following factories in your project classes:
Java Interface
| com.f1.ami.web.datafilter.AmiWebDataFilterPlugin
com.f1.ami.web.datafilter.AmiWebDataFilterinstance
|
These factories contain all the methods to needed for creating custom code.
Properties
You will also need to include the completed plugin in your AMI local.properties
:
| ami.web.data.filter.plugin.class=fully_qualified_class_name
|
General Implementation
-
Use the com.f1.ami.web.auth.AmiAuthenticator
plugin to authenticate a user and return a set of variables that are assigned to the user's session. These variables will be used to set the flags for the data filter.
-
This user-session is passed into the com.f1.ami.web.datafilter.AmiWebDataFilterPlugin
which then returns a com.f1.ami.web.datafilter.AmiWebDataFilterinstance
.
-
As data is passed from the backend to the frontend, the user's AmiWebDataFilter
is called first which determines whether to suppress the data.
Data filters can be applied to both real-time and static datamodel queries. The methods to implement are below.
Realtime
As data is streamed into AMI, individual records are transported to the frontend for display on a per-row basis.
- Rows can be added using
AmiWebDataFilter::evaluateNewRow(...)
.
- Rows can be updated using
AmiWebDataFilter::evaluateUpdateRow(...)
.
Deciding how rows are added or updated is determined by flags. The flags determines when the data filter is run and the output.
Visibility Flags
Flag |
Behavior |
HIDE_ALWAYS |
The row is always hidden after the filter is run (the filter does not re-run) |
SHOW_ALWAYS |
The row is always shown after filter is run (the filter does not re-run) |
HIDE |
The row is hidden until updated, then the filter is re-run |
SHOW |
The row is shown until updated, then the filter is re-run |
Static Query Results
When the user invokes a query (generally via the EXECUTE
command within a datamodel), a query object is constructed and sent to the backend for execution. Then, the backend responds with a table (or multiple tables) of data.
- Query request is sent as a message via
AmiWebDataFilter::evaluateQueryRequest(...)
.
- Apply the logic for filtering the response/output from the datamodel with
AmiWebDataFilter::evaluateQueryResponse(...)
Example
This example implements a simple data filter that checks if a user has an assigned "region" before determining whether to show or hide data.
In local.properties
, add the package and class names used in the data filter plugin java file to the corresponding property:
| ami.web.data.filter.plugin.class=data_filter.SampleDataFilterPlugin
|
This requires two Java classes: the plugin adapter (which will be set in local.properties
) and the functionality.
Plugin Factory
| package data_filter;
import com.f1.ami.web.datafilter.AmiWebDataFilter;
import com.f1.ami.web.datafilter.AmiWebDataFilterPlugin;
import com.f1.ami.web.datafilter.AmiWebDataSession;
import com.f1.container.ContainerTools;
import com.f1.utils.PropertyController;
public class SampleDataFilterPlugin implements AmiWebDataFilterPlugin {
@Override
public AmiWebDataFilter createDataFilter(AmiWebDataSession session) {
return new SampleDataFilter(session);
}
@Override
public void init(ContainerTools tools, PropertyController props) {
}
@Override
public String getPluginId() {
return "DATAFILTER_PLUGIN";
}
}
|
Data Filter Factory
The implementation of the filter log itself is contained in a separate Java class that implements the actual AmiWebDataFilter
factory.
- Realtime feeds are filtered using
evaluateNewRow
and evaluateUpdatedRow
.
- Datamodel quieres are filtered with
evaluateQueryRequest
and evaluateQueryResponse
.
| package data_filter;
import com.f1.ami.web.AmiWebObject;
import com.f1.ami.web.datafilter.AmiWebDataFilter;
import com.f1.ami.web.datafilter.AmiWebDataFilterQuery;
import com.f1.ami.web.datafilter.AmiWebDataSession;
import com.f1.base.Column;
import com.f1.base.Row;
import com.f1.utils.structs.table.columnar.ColumnarTable;
public class SampleDataFilter implements AmiWebDataFilter {
private AmiWebDataSession userSession;
private String allowedRegion;
public SampleDataFilter(AmiWebDataSession session) {
this.userSession = session; // Grabs the user session
allowedRegion = (String) userSession.getVariableValue("region"); // Checks for the "region" variable, which would have been assigned by a data filter
if(allowedRegion==null)
throw new RuntimeException("no 'region' specified");
}
@Override
public void onLogin() {
//Code to implement when the user logs in;
}
@Override
public void onLogout() {
//Code to implement when the user logs out;
}
@Override
public byte evaluateNewRow(AmiWebObject realtimeRow) {
String region = (String) realtimeRow.getParam("region");
return allowedRegion.equals(region) ? SHOW_ALWAYS : HIDE_ALWAYS;
}
@Override
public byte evaluateUpdatedRow(AmiWebObject realtimeRow, byte currentStatus) {
Object region = (String) realtimeRow.getParam("region");
return allowedRegion.equals(region) ? SHOW_ALWAYS : HIDE_ALWAYS;
}
@Override
public void evaluateQueryResponse(AmiWebDataFilterQuery query, ColumnarTable table) {
Column regionColumn = table.getColumnsMap().get("region");
if (regionColumn == null)
return;
for (int i = table.getSize() - 1; i >= 0; i--) {
Row row = table.getRow(i);
String region = row.getAt(regionColumn.getLocation(), String.class);
if (!allowedRegion.equals(region))
table.removeRow(row);
}
}
@Override
public AmiWebDataFilterQuery evaluateQueryRequest(AmiWebDataFilterQuery query) {
return query;
}
}
|
Blank WebDataFilter
Java File
| package com.f1.ami.web.datafilter;
import com.f1.ami.web.AmiWebObject;
import com.f1.ami.web.datafilter.AmiWebDataFilter;
import com.f1.ami.web.datafilter.AmiWebDataFilterQuery;
import com.f1.ami.web.datafilter.AmiWebDataSession;
import com.f1.base.Column;
import com.f1.base.Row;
import com.f1.utils.structs.table.columnar.ColumnarTable;
public interface AmiWebDataFilter {
public static final byte HIDE_ALWAYS = 1;
public static final byte SHOW_ALWAYS = 2;
public static final byte HIDE = 3;
public static final byte SHOW = 4;
/**
* Called the user logs
*
*/
public void onLogin();
/**
* Called when the user logs out
*
*/
public void onLogout();
/**
* Called when a new row is received from the Center. Note that during login, all rows for display will pass through this method
*
* @param realtimeRow
* the row to filter
* @return {@link #HIDE_ALWAYS} - The row will be hidden from the user, regardless of any subsequent updates to the row's data (less overhead)<BR>
* {@link #HIDE} - The row will be hidden from the user, but future updates to the row's data will be re-evaluated via
* {@link #evaluateUpdatedRow(AmiWebObject) }(greater overhead)<BR>
* {@link #SHOW_ALWAYS} - The row will be visible to the user, regardless of any subsequent updates to the row's data (less overhead)<BR>
* {@link #SHOW} - The row will be visible the user, but future updates to the row's data will be re-evaluated via {@link #evaluateUpdatedRow(AmiWebObject)} (greater
* overhead)<BR>
*
*/
public byte evaluateNewRow(AmiWebObject realtimeRow);
/**
* Called when a new row is updated from the Center. Note that this is only called for realtimeRows whose prior evaluation either returned {@link #HIDE} or {@link #SHOW}
*
* @param realtimeRow
* the row to filter
* @param currentStatus
* Either {@link #HIDE} or {@link #SHOW}
* @return {@link #HIDE_ALWAYS} - The row will be hidden from the user, regardless of any subsequent updates to the row's data (less overhead)<BR>
* {@link #HIDE} - The row will be hidden from the user, but future updates to the row's data will be re-evaluated via {@link #evaluateUpdatedRow(AmiWebObject)}(greater
* overhead)<BR>
* {@link #SHOW_ALWAYS} - The row will be visible to the user, regardless of any subsequent updates to the row's data (less overhead)<BR>
* {@link #SHOW} - The row will be visible the user, but future updates to the row's data will be re-evaluated via {@link #evaluateUpdatedRow(AmiWebObject)} (greater
* overhead)<BR>
*
*/
public byte evaluateUpdatedRow(AmiWebObject realtimeRow, byte currentStatus);
/**
* This method is called after each EXECUTE completes. In the general case, the Rows of the Table of the will be evaluated and certain rows may be deleted.
*
* @param query
* - The query passed to the backend.
* @param table
* - the resulting table from the query
*/
public void evaluateQueryResponse(AmiWebDataFilterQuery query, ColumnarTable table);
/**
* Called before the request is sent to the backend. If the query is permitted as is, simply return the query param. To reject the query return null. Or create a new
* {@link AmiWebDataFilterQueryImpl} object and set the various parameters that should actually be executed
*
* @param query
* the query that the user would like to run
* @return the actually query to run, or null if the query should not be executed.
*/
AmiWebDataFilterQuery evaluateQueryRequest(AmiWebDataFilterQuery query);
}
|