Content Filtering in Data Services with User Roles
- By Anjana Fernando
- |
- 6 Jan, 2010
Applies To
WSO2 Data Services Server | 2.2.0 |
Table of Contents
- Sales Division
- Finance Division
The company's database contains very detailed data about its products where the buying price is also mentioned. But this data must be moderated where the sales division must not see the buying price, but the finance division is allowed to access it. This requirement can be achieved in WSO2 Data-Services with the usage of 'user roles'. The following sections will take you through a step by step guide into how this functionality can be applied in helping 'East Coast Toys' in fulfilling their requirements.
Prerequisites
- Download WSO2 Data-Services latest build from https://wso2.com/products/data-services-server/.
- Download and install Apache Ant from https://ant.apache.org/ (optional: for the re-deployment of samples).
- Install WSO2 Data-Services as a stand-alone server (Install location will be referred to as DS_HOME here after).
- Start the server (Run DS_HOME/bin/wso2server.bat | wso2server.sh).
- Open a web browser and navigate to https://localhost:9443/carbon.
- For first time use, login using the default credentials: username=admin, password=admin.
Setting up the Server
In this section, the steps will be shown on how to create the necessary data service, and the users who would be accessing it.
For this demonstration, we will be creating two new users. 'tucker' and 'smith', who works for the sales and finance divisions respectively. Click the 'User Management' under the 'Configure' section in the menu. You will be presented with a screen similar to shown in Figure 1.1.

Figure 1.1: User List Screen
Click 'Add New User' and fill in the fields as shown below.Click 'Add New User' and fill in the fields as shown below.
User Name | tucker |
Password | secret_1 |

Figure 1.2: Add New User Screen
After the data is entered, click finish to create the user.
By following the same steps, add another user with the following fields.
User Name | smith |
Password | secret_2 |
Figure 1.3: User List After New User Added
Step 2 – Create New Roles and Assign them to Users
Under 'User Management', click Roles, and you will be presented with a screen as shown in Figure 2.1.
Figure 2.1: Roles List Screen
Click 'Add New Role' and set the 'Role Name' as 'sales'. After that, click 'Next', and you will be presented with a screen as shown in Figure 2.2 to select the permissions. In this instance, we will just give all the permissions available by clicking 'Select All'.
Figure 2.2: Select Role Permissions Screen
Click 'Next' in the 'Add Role' page, and you will arrive in the 'Select user to add to Role' page. Here you can select a desired user who will be added the current role. In the search box, type 'tucker' and click 'Search', shortly the 'tucker' will be visible in the list. Click the check box in front of it, and click 'Finish' to assign the role to the user.
Figure 2.3: Adding the Role to an User
Following the same steps, add another role with the name 'finance' and add the user 'smith' to the role.
After following the previous steps, the roles list will contain the newly created roles, as shown in Figure 2.4.
Figure 2.4: Roles List After New Role Added
Now the necessary users and their roles are set up, we are ready to define the data service the users will be consuming.
Step 3 – Edit Data Service Query
Click 'List' under 'Services' section, to list the currently deployed services and you should be presented with a screen similar to that shown in Figure 3.1.Click 'List' under 'Services' section, to list the currently deployed services and you should be presented with a screen similar to that shown in Figure 3.1.
Figure 3.1: Deployed Services List
These services will be deployed by default with the WSO2 Data Services solution. Here, the 'DataServiceSample1' sample will be used to model the company's requirements.
Note: If the sample services are not deployed by default they can be deployed by navigating to the directory 'DS_HOME/samples/DataService/' and running the 'ant' command.
Click on the 'DataServiceSample1' service and follow the data service editing wizard up to the query listing page, as shown in Figure 3.2.
Figure 3.2: Query List
Edit the 'productsSQL' query, which will result in the screen shown in Figure 3.3.
Figure 3.3: Edit Query Screen
Here the list of output mappings can be seen in the query editing page. The output mappings represent the elements of the resultant XML of the data service call. The 'Allowed User Roles' field controls the access to this data for a specific user.
This behavior is simply enforced by checking the service invoker's assigned user roles. If the user has the required roles of an output mapping, then that data section will be returned, otherwise it will not be returned with the result.
The criteria for filtering data for the 'productsSQL' query will be as follows.
- All fields will be visible to users with the "finance" role.
- All fields except the 'buyPrice' field will be visible to users with the "sales" role.
- Other users who doesn't have the roles "sales" nor "finance" will not see any of the fields.
Click on 'Edit' on the output fields as mentioned above and select the respective user roles as mentioned above, Figure 3.4 shows the assignment of user roles to the 'buyPrice' field.
Note: For an output field to be visible to all users, you can choose not to select any user roles from the list, or select the special 'everyone' user role which is inherent in all the users.
Figure 3.4: Setting Allowed Roles for an Output Field
After all the required roles have been set for the result entries the Edit Query page should resemble Figure 3.5.
Figure 3.5: Edit Query Page with the Required Roles Set
Step 4 – Enable User Authentication for the Data Service
In order for the service to identify the user who is sending requests security must be enabled for it and a method for authentication must be provided. Here we will be enabling UsernameToken for authenticating the current user.
At the services list, click on the data service we were currently editing, and it will take you to the service dashboard, as seen in Figure 4.1.
Figure 4.1: Service DashboardService Dashboard
Click on security, and in the next screen, set 'Yes' to enable security. There you will be given options to select the type of authentication. Under the 'Basic Scenarios' section, select 'UsernameToken'. These steps are shown in Figure 4.2.
Figure 4.2: Set User Authentication Method Screen
Click 'Next' and it will take you to a screen where you should specify the user groups that are allowed to access the service. For now, we will select 'everyone' which will allow all user groups to access the service, after that click 'Finish'.
Figure 4.3: Security - Select User Groups Screen
Consuming the Service
This section shows the possible ways, the above defined data service can be consumed. In earlier stages when testing a service, the built-in 'tryit' can be used. But in a production environment, a client application must be written. The upcoming sections present both approaches.
In the services list, click on the 'Try this service' link at the 'DataServiceSample1''s entry. There you will be taken to a screen where you will be presented with fields to enter user credentials and choose the operation to be invoked. The operation to invoke is 'productsInfo' which uses our modified user roles aware query. The operations are invoked separately with the following users.
- smith
Figure 5.1: Data Service Result with User 'smith'
In Figure 5.1, we see that all the information about a product is shown, this is bacause, the user 'smith' has the 'finance' role, and all the fields in product information support that specific role.
- tucker
Figure 5.2: Data Service Result with User 'tucker'
Invoking the service with the user 'tucker', all the fields except the 'buyPrice' is returning. The buyPrice field is not available to this user, because he does not have the 'finance' role, which is required for that field.
- admin
Figure 5.3: Data Service Result with User 'admin'
The user 'admin' does not have any of the required roles for product information, thus the data services result contains empty elements.
A client stub can be automatically generated using the 'WSDL2Code' tool. This can be accessed by navigating to the service dashboard and clicking on the 'Generate Client' link. There you will be given the option to change several parameters used in the code generation. Here we will be using ADB (Axis2 Data Binding) as the data binding mechanism for the service schema types. Below table contains some of the useful parameters and its values that can be provided.
-p | org.example.ds1 |
-d | adb |
-ns2p | https://ws.wso2.org/dataservice/data_service_sample1=org.example.types.ds1 |
-u |
[checked] |
-uw | [checked] |
Figure 5.4: WSDL2Code Tool
After the necessary fields are filled, click the 'Generate' button, where you will be presented with a zip file which contains the generated client code.
The generated code will contain a package 'org.example.ds1'. We will be adding a new class 'Client' which will use the generated stubs. The source code for the client class is listed below.
Client.java
package org.example.ds1; import java.io.File; import org.apache.axiom.om.impl.builder.StAXOMBuilder; import org.apache.axis2.client.Options; import org.apache.axis2.context.ConfigurationContext; import org.apache.axis2.context.ConfigurationContextFactory; import org.apache.neethi.Policy; import org.apache.neethi.PolicyEngine; import org.apache.rampart.RampartMessageData; import org.wso2.ws.dataservice.Product; public class Client { private static Policy loadPolicy(String xmlPath) throws Exception { StAXOMBuilder builder = new StAXOMBuilder(xmlPath); return PolicyEngine.getPolicy(builder.getDocumentElement()); } public static void main(String[] args) throws Exception { /* change the following variables suitably */ String ds_home = "/home/laf/dev/bin/wso2dataservices-2.2.0"; String policyFilePath = "/home/laf/Desktop/policy.xml"; String epr = "https://localhost:9443/services/DataServiceSample1"; /* set security settings */ System.setProperty("javax.net.ssl.trustStore", ds_home + File.separator + "resources" + File.separator + "security" + File.separator + "wso2carbon.jks"); System.setProperty("javax.net.ssl.trustStorePassword", "wso2carbon"); ConfigurationContext ctx = ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, null); DataServiceSample1Stub stub = new DataServiceSample1Stub(ctx, epr); stub._getServiceClient().engageModule("rampart"); Options options = stub._getServiceClient().getOptions(); options.setProperty(RampartMessageData.KEY_RAMPART_POLICY, loadPolicy(policyFilePath)); stub._getServiceClient().setOptions(options); /* invoke service */ Product[] products = stub.productsInfo(); System.out.println("Customers:-"); for (Product product : products) { System.out.println("\t---------------"); System.out.println("\tProduct Code: " + product.getProductCode()); System.out.println("\tMSRP: " + product.getMSRP()); System.out.println("\tProduct Line: " + product.getProductLine()); System.out.println("\tProduct Name: " + product.getProductName()); System.out.println("\tProduct Scale: " + product.getProductScale()); System.out.println("\tProduct Vendor: " + product.getProductVendor()); System.out.println("\tBuy Price: " + product.getBuyPrice()); } } }
The variable 'ds_home' and 'policyFilePath' paths must be set suitably for the user's environment. For enabling security, Rampart requires the user to provide a policy file, there a password callback handler class is defined. The source code for the password callback handler class is stated below.
package org.example.ds1; import java.io.IOException; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import org.apache.ws.security.WSPasswordCallback; public class PWCBHandler implements CallbackHandler { public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback cb : callbacks) { WSPasswordCallback pwcb = (WSPasswordCallback) cb; String id = pwcb.getIdentifer(); if ("tucker".equals(id)) { pwcb.setPassword("secret_1"); } else if ("smith".equals(id)) { pwcb.setPassword("secret_2"); } else if ("admin".equals(id)) { pwcb.setPassword("admin"); } } } }
The policy file contains the fully qualified name of the password callback class as 'org.example.ds1.PWCBHandler'. This file can be downloaded from the attachments section at the end of the article.
After the necessary files are in place, we will modify the auto generated ant build file – build.xml, to add a new build target to run the newly created client class. In the build file, add the following lines.
<target name="run" depends="jar.client"> <java fork="true" classname="org.example.ds1.Client"> <classpath> <path refid="axis2.class.path"></path> <path location="${lib}/${name}-test-client.jar"></path> </classpath> </java> </target>
Setting the Classpath
Before running the ant tool for compiling the code and running it, we should set the classpath pointing to the Axis2 libraries. The ant build file uses an environment variable 'AXIS2_HOME' which it expects to point to a directory which contains the Axis2 library jar files. The expected value for AXIS2_HOME is DS_HOME/repository (replace DS_HOME appropriately).
e.g. :-
Unix/Linux:
# export AXIS2_HOME=/home/laf/home/laf/dev/bin/wso2dataservices-2.2.0/repository
Windows:
# set AXIS2_HOME=c:\wso2dataservices-2.2.0\repository
After modifying the build.xml and setting AXIS2_HOME, run the command 'ant run' to run the client. The output should resemble the following.
Buildfile: build.xml init: pre.compile.test: [echo] Stax Availability= true [echo] Axis2 Availability= true compile.src: jar.client: run: [java] log4j:WARN No appenders could be found for logger (org.apache.axis2.util.Loader). [java] log4j:WARN Please initialize the log4j system properly. [java] Customers:- [java] --------------- [java] Product Code: S10_1678 [java] MSRP: 95.7 [java] Product Line: Motorcycles [java] Product Name: 1969 Harley Davidson Ultimate Chopper [java] Product Scale: 1:10 [java] Product Vendor: Min Lin Diecast [java] Buy Price: NaN [java] --------------- . . . [java] Product Code: S72_1253 [java] MSRP: 49.66 [java] Product Line: Planes [java] Product Name: Boeing X-32A JSF [java] Product Scale: 1:72 [java] Product Vendor: Motor City Art Classics [java] Buy Price: NaN [java] --------------- [java] Product Code: S72_3212 [java] MSRP: 54.6 [java] Product Line: Ships [java] Product Name: Pont Yacht [java] Product Scale: 1:72 [java] Product Vendor: Unimax Art Galleries [java] Buy Price: NaN BUILD SUCCESSFUL
Notice that the 'Buy Price' field always returns as 'NaN' in the results which stands for 'Not a Number'. This is because the user 'tucker' who is mentioned in the policy.xml does not have the rights to access that field in the data service. You can change the user in the policy file and see the changes in the result.
Summary
In this article, we have discussed how a data service result can be moderated according to the user who is accessing it by using WSO2 Data Services Server. First, we demonstrated how the server was configured to support this functionality by creating the necessary users and roles and then how the data service results were mapped appropriately to the user roles. And finally, this functionality is put in action by consuming the data service via a custom Java client application.
Author
Anjana Fernando. Software Engineer. WSO2 Inc. anjana at wso2 dot com