Jun 12, 2014

In this post i will share the security utility for performing operations on IAM Fortress applications using it’s java API’s. Use this utility only if you plan on having custom code to manage Fortress as fortress already provides a J2ee application to manage its policies. The code can be used coupled with earlier examples to integrate fortress as a security provider for your ADF essentials application. Also, you can make task flows for performing user, role, permission management using this utility.  The code is mentioned below

Posted on Thursday, June 12, 2014 by raman nanda

May 23, 2014

Recently a user contacted me with the requirement that he needed to assign enterprise groups to application roles dynamically. Normally, people can manage this through enterprise manager, but there might have been a specific requirement for that user.  I have already written various posts on ADF security which cover utility methods that use OPSS API’s to perform operations on the enterprise OID, OpenLdap etc.  In this post i will cover this specific requirement; although the given code is very crude, it is there only to serve as an example and the process is what matters.

The code is shown below.

package model;

import oracle.security.jps.service.policystore.ApplicationPolicy;
import oracle.security.jps.service.policystore.PolicyStore;
import java.security.Principal;

import java.util.Hashtable;

import oracle.adf.share.logging.ADFLogger;

import oracle.security.idm.IMException;
import oracle.security.idm.IdentityStore;
import oracle.security.idm.IdentityStoreFactory;
import oracle.security.idm.IdentityStoreFactoryBuilder;
import oracle.security.idm.Role;
import oracle.security.idm.RoleManager;
import oracle.security.idm.User;
import oracle.security.idm.UserManager;
import oracle.security.idm.providers.oid.OIDIdentityStoreFactory;
import oracle.security.jps.JpsContext;
import oracle.security.jps.JpsContextFactory;
import oracle.security.jps.JpsException;
import oracle.security.jps.internal.idstore.ldap.LdapIdentityStore;
import oracle.security.jps.service.idstore.IdentityStoreException;
import oracle.security.jps.service.idstore.IdentityStoreService;
import oracle.security.jps.service.policystore.ApplicationPolicy;
import oracle.security.jps.service.policystore.PolicyObjectNotFoundException;
import oracle.security.jps.service.policystore.PolicyStore;
import oracle.security.jps.service.policystore.PolicyStoreException;
import oracle.security.jps.service.policystore.info.common.InvalidArgumentException;

public class DemoJpsMethods {
public static final ADFLogger DemoJpsLogger=ADFLogger.createADFLogger(DemoJpsMethods.class);
public static final String DEV_GROUP="DevGroup";
public static final String TRAINING_GROUP="TrainingGroup";
public static final String DEV_APP_ROLE="DevAppRole";
public static final String TRAINING_APP_ROLE="TrainingAppRole";
public DemoJpsMethods() {
super();
}

/**
* Dummy method just for testing so not parameterized
*/
public void addUserToAppGroup(){
JpsContext ctxt = IdentityStoreConfigurator.jpsCtxt;
LdapIdentityStore idstoreService =
(LdapIdentityStore)ctxt.getServiceInstance(IdentityStoreService.class);
IdentityStore idmStore=null;
try {
idmStore = idstoreService.getIdmStore();

} catch (IdentityStoreException e) {
throw new RuntimeException(e);
}
User tom=null;
User helen=null;
UserManager um=null;
RoleManager rm=null;
Role trainingRole=null;
Role devRole=null;

try {
um= idmStore.getUserManager();
rm=idmStore.getRoleManager();
} catch (IMException e) {
try {
idmStore.close();
} catch (IMException f) {
}
throw new RuntimeException(e);
}
try {
trainingRole =
idmStore.searchRole(IdentityStore.SEARCH_BY_NAME, TRAINING_GROUP);
devRole =
idmStore.searchRole(IdentityStore.SEARCH_BY_NAME, DEV_GROUP);
} catch (IMException e) {
DemoJpsLogger.severe("Rethrow exception because roles ARE NOT found");
try {
idmStore.close();
} catch (IMException f) {
}
throw new RuntimeException(e);
}


try {
tom = idmStore.searchUser("tom");
helen = idmStore.searchUser("helen");


//if it comes till here user is found

} catch (IMException e) {
DemoJpsLogger.severe("Ignore exception because user is likely not found");
}
//User not found create ?
if(tom==null){
try {
tom= um.createUser("tom", "Welcome_1".toCharArray());

} catch (IMException e) {
try {
idmStore.close();
} catch (IMException f) {
}
throw new RuntimeException(e);

}

}
//User not found create ?
if(helen==null){
try {
helen=um.createUser("helen", "Welcome_1".toCharArray());

} catch (IMException e) {
try {
idmStore.close();
} catch (IMException f) {
}
throw new RuntimeException(e);
}

}
try {
rm.grantRole(trainingRole,tom.getPrincipal());
rm.grantRole(devRole,helen.getPrincipal());
} catch (IMException e) {
try {
idmStore.close();
} catch (IMException f) {
}
throw new RuntimeException(e);
}

if(idmStore!=null){
try {
idmStore.close();
} catch (IMException f) {
}
}



}
public void addAppGroupToAppRole() {
JpsContext ctxt = IdentityStoreConfigurator.jpsCtxt;
LdapIdentityStore idstoreService =
(LdapIdentityStore)ctxt.getServiceInstance(IdentityStoreService.class);
IdentityStore idmStore=null;
try {
idmStore = idstoreService.getIdmStore();
} catch (IdentityStoreException e) {
throw new RuntimeException(e);
}
PolicyStore ps = ctxt.getServiceInstance(PolicyStore.class);
ApplicationPolicy policy;
Role trainingRole=null;
Role devRole=null;
try {
trainingRole =
idmStore.searchRole(IdentityStore.SEARCH_BY_NAME, TRAINING_GROUP);
devRole =
idmStore.searchRole(IdentityStore.SEARCH_BY_NAME, DEV_GROUP);
} catch (IMException e) {
try {
idmStore.close();
} catch (IMException f) {
}
throw new RuntimeException(e);
}



try {
policy = ps.getApplicationPolicy("DemoAppSecurity#V2.0");
policy.addPrincipalToAppRole(trainingRole.getPrincipal(), TRAINING_APP_ROLE);
policy.addPrincipalToAppRole(devRole.getPrincipal(), DEV_APP_ROLE);
} catch (PolicyStoreException e) {
throw new RuntimeException(e);
} catch (IMException e) {
throw new RuntimeException(e);
}

if(idmStore!=null){
try {
idmStore.close();
} catch (IMException f) {
}
}

}



/**
* This nested private class is used for configuring and initializing the context factory
* @author Ramandeep Nanda
*/
private static final class IdentityStoreConfigurator {
private static final JpsContext jpsCtxt=initializeFactory();


private static JpsContext initializeFactory(){
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
JpsContextFactory tempFactory;
JpsContext jpsContext;
try {
tempFactory=JpsContextFactory.getContextFactory();
jpsContext=tempFactory.getContext();
}
catch (JpsException e) {
DemoJpsLogger.severe("Exception in "+methodName + " " +e.getMessage() +" ", e);
throw new RuntimeException("Exception in "+methodName + " " +e.getMessage() +" ", e);
}
return jpsContext;
}



}
}


 



So basically there are two methods, the first one just creates the user and assigns them to the enterprise groups, the second one is the one we’re interested in because this one first accesses the application policy and then dynamically assigns the enterprise group to the application role .



We are not done just yet, there are a few steps you need to configure which are mentioned below.




  • Assign PolicyStoreAccessPermission to weblogic user in system-jazn-data.xml.
    <grant>
    <grantee>
    <principals>
    <principal>
    <class>weblogic.security.principal.WLSUserImpl</class>
    <name>weblogic</name>
    </principal>
    </principals>
    </grantee>
    <permissions>
    <permission>
    <class>oracle.security.jps.service.policystore.PolicyStoreAccessPermission</class>
    <name>context=SYSTEM</name>
    <actions>*</actions>
    </permission>
    </permissions>
    </grant>



  • Although i have added the application stripe grant in the application jazn-data.xml, you need to be aware of that. It is mentioned below:
      <grant>
    <grantee>
    <principals>
    <principal>
    <class>oracle.security.jps.internal.core.principals.JpsXmlUserImpl</class>
    <name>weblogic</name>
    </principal>
    </principals>
    </grantee>
    <permissions>
    <permission>
    <class>oracle.security.jps.service.policystore.PolicyStoreAccessPermission</class>
    <name>context=APPLICATION,name=DemoAppSecurity#V2.0</name>
    <actions>*</actions>
    </permission>
    </permissions>
    </grant>


     



  • Run the application and log in with weblogic user i.e login.html page


  • There will be two buttons one creates and adds a user to a group, second adds enterprise group to app role


  • For first, You need an ldap browser to view the changes, use the embedded ldap connection details.


  • For second, you need just to view the system-jazn-data.xml file, here you will see enterprise group added to app role.



The application can be downloaded from here. Application Link

Posted on Friday, May 23, 2014 by raman nanda

The in built primefaces charting components can be extended like any other faces component. In this post i will explain how to extend the in built pie chart component to use custom renderer which in turn will use NVD3 pie chart component. On a similar line you can similarly create your own custom faces component also.

The code is mentioned below.

package com.blogspot.ramannanda.ui.chart;

import java.io.IOException;
import java.util.Iterator;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import org.primefaces.component.chart.UIChart;
import org.primefaces.component.chart.line.LineChart;
import org.primefaces.component.chart.pie.PieChart;
import org.primefaces.component.chart.pie.PieChartRenderer;
import org.primefaces.model.chart.PieChartModel;

public class NVD3PieChartRenderer extends PieChartRenderer {
@Override
public void encodeEnd(FacesContext context, UIComponent component) throws IOException{
PieChart chart = (PieChart) component;

encodeMarkup(context, chart);
encodeScript(context, chart);
}
@Override
protected void encodeScript(FacesContext context, UIChart uichart) throws IOException{
ResponseWriter writer = context.getResponseWriter();
PieChart chart = (PieChart) uichart;
String clientId = chart.getClientId(context);
String d3Id=chart.getClientId(context);
d3Id="pieChart"+d3Id.substring(d3Id.lastIndexOf(":")+1, d3Id.length());
startScript(writer, clientId);
encodeData(context,chart);
writer.write("nv.addGraph(function() {"+
"var chart =nv.models.pieChart()"+
".x(function(d) { return d.key })"+
".y(function(d) { return d.value })"+
".color(d3.scale.category20().range())"
+ ".margin({top: 30, right: 30, bottom: 30, left: 30});");
writer.write("d3.select('#"+d3Id+" svg')"+
".datum(mydata"+d3Id+")"+
".call(chart);");
writer.write("nv.utils.windowResize(chart.update);");
writer.write("return chart; });");
endScript(writer);

}
@Override
protected void encodeData(FacesContext context, PieChart chart) throws IOException {
ResponseWriter writer = context.getResponseWriter();
String d3Id=chart.getClientId(context);
d3Id="pieChart"+d3Id.substring(d3Id.lastIndexOf(":")+1, d3Id.length());
writer.write("var mydata"+d3Id+"=[" );
PieChartModel model = (PieChartModel) chart.getValue();

for(Iterator<String> it = model.getData().keySet().iterator(); it.hasNext();) {
String key = it.next();
Number value = model.getData().get(key);
if(value==null){
value=0;
}
writer.write("{key:'" + escapeText(key) + "',value:'" + value + "'}");

if(it.hasNext())
writer.write(",");
}

writer.write("];");

}
@Override
protected void encodeMarkup(FacesContext context, UIChart chart)
throws IOException {
ResponseWriter writer = context.getResponseWriter();
String d3Id=chart.getClientId(context);

writer.startElement("div", null);
writer.writeAttribute("id", "pieChart"+d3Id.substring(d3Id.lastIndexOf(":")+1, d3Id.length()), null);
if(chart.getStyle() != null)
writer.writeAttribute("style", chart.getStyle(), "style");
writer.writeAttribute("class", "chart1", "styleClass");
writer.writeAttribute("align", "center", "align");
writer.startElement("h3",null);
writer.writeText(chart.getTitle(),null);
writer.endElement("h3");
writer.startElement("svg",null);
writer.endElement("svg");

writer.endElement("div");

}

}


 



If you had followed the previous intro to d3.js and you are aware with JSF, this code sample should be easy to follow. Basically we create a div element with the client id and then svg element under it, we then do a selection of the svg element under the div. Here the thing to note is that we are using datum function rather than data function, whereas the data function computes a join, datum function just binds the data to the existing element and does not compute the join.



To register the renderer add the following into faces configuration file.



  <renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.chart.PieChartRenderer</renderer-type>
<renderer-class>com.blogspot.ramannanda.ui.chart.NVD3PieChartRenderer</renderer-class>
</renderer>


Also make sure to include the following files as they are part of nvd3 and d3 libraries into your *.xhtml page.



<link href="../src/nv.d3.css" rel="stylesheet" type="text/css">
<script src="../lib/d3.v3.js"></script>
<script src="../nv.d3.js"></script>

<script src="../src/models/legend.js"></script>

<script src="../src/models/pie.js"></script>

<script src="../src/models/pieChart.js"></script>

<script src="../src/utils.js"></script>
<script>
<style type="text/css">

body {
overflow-y:scroll;
overflow-x:scroll;
}

text {
font: 12px sans-serif;
}

svg {
display: block;
}

.chart1{
padding:10px;
min-width: 150px;
min-height: 150px;


}
</style>


And this is how the pie chart component now looks. You can select which data to show and the state of the pie chart changes to whatever data is selected along with a nice transition effect. Cool uh ?.



piechar1



piechart2

Posted on Friday, May 23, 2014 by raman nanda

D3.js is a framework for data driven documents. You can use it to create data visualizations. If you require some charting component for your java application you can easily create components based on this library or a derivative of it. In this post i will highlight some of its features via a html example and in the next post will give you an example of how you can change the renderer of prime faces component to use nvd3 component( A set of charting components built using  d3.js).

D3 has a syntax similar to jquery that means you can easily select, append, remove existing elements, chain method calls etc. I will first present the source code of the example and then explain it step by step.

<html>
<head>
<style>
.chart {
fill:steelblue;
}

.bar {
fill:steelblue;
}
.chart text {
fill: black;
font: 10px sans-serif;
text-anchor: middle;
}
.axis text {
font: 10px sans-serif;
}

.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}



div.tooltip {
position: absolute;
text-align: center;
width: 80px;
height: 40px;
padding: 8px;
font: 10px sans-serif;
background: #ddd;
border: solid 1px #aaa;
border-radius: 8px;
pointer-events: none;
}
</style>
<script src="d3/d3.js" charset="utf-8"></script>

</head>
<body>

<svg class="chart"></svg>

</body>
<script>
//Data to be used
var data = [{key:'Mathematics',value:80}, {key:'English',value:70},{key:'Physics',value:90},{key:'Biology',value:60},{key:'History',value:80},{key:'Chemistry',value:50},{key:'Geology',value:50}];

//specify margins
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 600 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
//calculate each barwidth dynamically
var barWidth=width/data.length;
//Since we do not have integer coordinates and only string labels the x axis scale is ordinal
//here we have specified only range and domain will be defined later
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .4);
//Since cordinates start at top so we want the higher values to have lower y
var y = d3.scale.linear()
.range([height, 0]);
//Define an x axis, specify the scale to use and orientation
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
//Define a Y axis
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// Selecting an element to create the initial g element under svg and translate the element to (x,y) of margins

var chart=d3.select(".chart").attr("width",width+margin.left + margin.right).attr("height",height+ margin.top + margin.bottom).append("g").attr("transform", "translate("+margin.left+","+margin.top+")");
//Now we specify the domains here x domain is special because we use map function to map each key and hence construct a domain for x axis
x.domain(data.map(function(d) { return d.key; }));
//y domain is simply from 0 to the maximum value of data, here we used data with a function to calculate the maximum of associative array
y.domain([0, d3.max(data, function(d) { return d.value;})]);

//Now Use enter selection to Append rectangle, notice y is given the value of hieght so that the bars are at base, add showtop function to //show tooltips
chart.selectAll(".bar").data(data).enter().append("rect").attr("class","bar").attr("x",function(d,i){return x(d.key);}).
attr("y",height).attr("width",x.rangeBand()).attr("height",0).on("mousemove",showTip);
//append the tooltip element and set its opacity to invisible value

var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 1e-6);
//Call x and yaxis functions and append them to the chart div element
chart.append("g").attr("class","x axis").attr("transform", "translate(0,"+height+")").call(xAxis);
chart.append("g")
.attr("class", "y axis")
.call(yAxis);
//This is where we set the actual values of the y positions and give the bars the transition effect
chart.selectAll(".bar").transition().duration("1000").attr("y",function(d,i){return y(d.value);}).attr("height",function(d,i){ return height-y(d.value);});
//give a olive transition effect on mouseover
chart.selectAll(".bar").on("mouseover",function (){d3.select(this).transition().duration("1000").style("fill","olive");mouseoverTip();
});

function showTip(d) {
div.text("Scored "+d.value+" in "+ d.key)
.style("left", (d3.event.pageX - 34) + "px")
.style("top", (d3.event.pageY - 12) + "px");
}
//onmouseout transition
chart.selectAll(".bar").on("mouseout",function (){
d3.select(this).transition().duration("1000").style("fill","steelblue");
mouseoutTip();
}
);



function mouseoutTip() {
div.transition()
.duration(500)
.style("opacity", 1e-6);
}
function mouseoverTip() {
div.transition()
.duration(500)
.style("opacity", 1);
}

//Append label to y axis

chart.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Subject Scores");



</script>
</html>


I have added explanation comments to the source code. Now I will explain the details of each section.




  • D3 Selections(Enter/Update/Exit):  In D3 you generally work with selections which binds your data to elements or element. You can think of them as joins. Also the data that is joined is available to all the functions in the join statement, this is important because you can then perform functions on the data.

    • Enter Selection : In case of enter selection when data is bound to elements all the non existing elements for which is there is not data are created. So let’s say you have initially no elements and want to create them, then you do a Enter Selection.  In this example enter selection is used to append rectangle elements and since no existing elements with class .bar exist, new ones will be created.


    • Update Selection: This is used to update the elements  with new  attribute values; The thing to note is that the elements must exist, only then will they be updated with new data values.


    • Exit Selection: This is the opposite of enter selection, here all the elements for which there is no data are removed.




  • Dynamic chart scaling: You generally define a scaling function for representing data on graph by dynamically mapping data values to their respective positions in the graph. The scaling function can be linear, ordinal etc. In the above mentioned example we do not have x coordinates but only String labels, so to map the labels to the corresponding positions, we use ordinal scale. The domain function accepts an array of values, So to generate the array of values from the data that we use javascript’s map function. The range function in case of x coordinates is rangeroundbands which divides the entire range into n bands(1. It gives exact integer values 2. Here n is the number of values in the domain 3. It accepts second argument which specifies spacing between elements). For y scale we use a linear scale because we want our values to scale linearly.


  • Coordinate System: If you are aware about element positioning, you will know that the top left corner represents the origin i.e x=0,y=0 position. So for us to draw the bars at the base, we give higher values of y in range to lower values in the domain, this is so because we have to subtract these values from height. So y becomes higher for lower values but height of the bar becomes lower.


  • Transitions: In this example we use two different types of transitions. 1) Position transition: If you followed the example you would have noticed that initial y position of the rect svg element is set to height, this is done because we want the bars to transition from base to their respective heights. Also transition requires that the elements must exist before it can occur, this is why it is done in the second step. 2) Style transition: If you hover your mouse over the bars, you would notice that the bar’s colour  changes to olive smoothly, this is done by changing the fill value of rect elements and using the transition function.


  • Events: The selected elements can easily be bound to events using the on() function. In this example transition and tooltip functions are bound to events such as mouseover, mouseout, mousemove etc.



The screenshot of the graph is given below. In the next post i will cover an example of how to extend a primefaces component to use nvd3 library.



 



d3example

Posted on Friday, May 23, 2014 by raman nanda

Jan 21, 2014

I was recently given a requirement to drive the entity attribute validations at runtime rather than at design time, with the following criteria.
  1. Ability to change fields to required at runtime.
  2. To be able to define validation message for field at runtime
  3. Define validations such as field must be numeric, or contain only alphabets (Used Regular expression for this)
  4. Localization of validation messages
To accomplish this we can use database dictionary views along with custom database tables to store the validations and perform validations at runtime. The data model diagram is shown below.
DEMO_RUNTIME_VALThe key thing then is to use database dictionary views along with these database tables to validate attributes of the entity for a pattern (regular expression).

The class that contains the method to perform validations is CustomEntityImpl and each entity that one wants to enable for runtime validation must extend this class and call its validateCustomEntity method in its entity method validator.

    protected Boolean validateCustomEntity(){
          
            ResourceBundleDef rbd=getEntityDef().getResourceBundleDef();
            PropertiesBundleDef pbd=null;
            if(rbd==null){
            pbd = new PropertiesBundleDef(this.getEntityDef());
            pbd.setPropertiesFile("com.blogspot.ramannanda.demos.validationapp.model.ModelBundle");
            rbd=pbd;
            }
            ArrayList<RowValException> exceptions=new ArrayList<RowValException>();
            EntityImpl entObj= this;
                       String tableName=this.getEntityDef().getSource();
                       tableName=tableName.substring(tableName.lastIndexOf(".")+1,tableName.length());
                       AttributeDef[] attrDefs= this.getEntityDef().getAttributeDefs();
                       
                       ValidationsVOImpl vo=(ValidationsVOImpl) this.getDBTransaction().getRootApplicationModule().findViewObject("ValidationsVO12");
                       ViewCriteriaManager vcm=vo.getViewCriteriaManager();
                       ViewCriteria vc=vcm.getViewCriteria("ValidationsVOCriteria");
                       vo.setbTableName(tableName);
                       vo.applyViewCriteria(vc, false);
                       vo.executeQuery();
                       RowSetIterator it=vo.createRowSetIterator(null);
                       ArrayList<AttrValException> attrValExceptions=new ArrayList<AttrValException>();
                       while(it.hasNext()){
                          ValidationsVORowImpl row=(ValidationsVORowImpl)it.next();
                          String pattern=row.getValidationPattern();
                          String fieldName=row.getAttribName();
                           
                               for(int i=0;i<attrDefs.length;i++){
                                    String attrName=attrDefs[i].getName();
                                    String columnName=attrDefs[i].getColumnName();
                                   if(columnName.equals(fieldName)){
                                       String attribRequired=row.getAttribRequired();
                                       Object attribValue=entObj.getAttribute(attrName);
                                      
                                       ArrayList<JboException> list=new ArrayList<JboException>(2);
                                       if(attribRequired.equalsIgnoreCase("Y"))
                                       {
                                           if(attribValue==null || ((String)attribValue).isEmpty()){
                                          String errorMessageKey=row.getRequiredMsgKey();
                                           if(errorMessageKey!=null&&!errorMessageKey.isEmpty()){
                                           JboException exception=new JboException(getMessageForKey(errorMessageKey),null,new Object[]{attrName});
                                                   list.add(exception);
                                               }
                                           else{
                                                   AttrSetValException exception=
                                                       new AttrSetValException(AttrValException.TYP_DEF_ENTITY_OBJECT,  rbd,
                                                      "DEFAULT_REQUIRED_ERROR", entObj.getStructureDef().getFullName(),
                                                       attrName,attribValue, null);
                                                       list.add(exception);
                                               }
                                            
                                           }
                                       }
                                       if(((attribValue!=null)&& !((String)attribValue).isEmpty())){
                                       boolean result=validatePattern(pattern,(String)entObj.getAttribute(attrName));
                                       if(!result){
                                          
                                               String errorMessageKey=row.getValidationMsgKey();
                                               if(errorMessageKey!=null&&!errorMessageKey.isEmpty()){
                                                       JboException exception=new JboException(getMessageForKey(errorMessageKey),null,new Object[]{attrName,attribValue});
                                                               list.add(exception);
                                                       list.add(exception);
                                                   }
                                               else{
                                                       AttrSetValException exception=
                                                           new AttrSetValException(AttrValException.TYP_DEF_ENTITY_OBJECT,  rbd,
                                                          "DEFAULT_INCORRECT_VAL_ERROR", entObj.getStructureDef().getFullName(),
                                                           attrName,attribValue, null);
                                                           list.add(exception);
                                                   }
                                           }
                                       }
                                       if(list.size()>0){
                                               AttrValException ave =
                                                        new AttrValException(CSMessageBundle.class, CSMessageBundle.EXC_VAL_VR_VALIDATE_FAILED, entObj.getStructureDef().getFullName(),
                                                                             attrName,attribValue, list, false);
                                               attrValExceptions.add(ave);
                                           }
                                       
                                       
                                   } 
                           }
                      
                   }
                       it.closeRowSetIterator();
                       if(attrValExceptions.size()>0){
                           RowValException rowValException= new RowValException(CSMessageBundle.class, CSMessageBundle.EXC_VAL_VR_VALIDATE_FAILED,
                                entObj.getStructureDef().getFullName(), entObj.getKey(), attrValExceptions);
                           exceptions.add(rowValException);
                           }

                 
     
                   if(exceptions.size()>0){
                       throw new
                        RowValException(CSMessageBundle.class, CSMessageBundle.EXC_VAL_VR_VALIDATE_FAILED,
                                                           getStructureDef().getFullName(), getKey(), exceptions);
                       }
            return true;
        
        }
   

    /**
     * This method returns false or true depending upon whether the field is valid or not
     * @param patternString
     * @param value
     */
    private boolean validatePattern(String patternString, String value) {
        Pattern pattern =Pattern.compile(patternString);
        Matcher matcher= pattern.matcher(value);
        return matcher.matches();
        
    }
    

    
    private String getMessageForKey(String msgKey){
            Locale defaultLocale=Locale.US;
            String defaultMessage=null;
            String localMessage=null;
            Locale locale = this.getDBTransaction().getSession().
              getLocale();
             String localString=locale.toLanguageTag();
            ValidationMessagesVOImpl vo=(ValidationMessagesVOImpl) this.getDBTransaction().getRootApplicationModule().findViewObject("ValidationMessagesVO12");
            ViewCriteriaManager vcm=vo.getViewCriteriaManager();
            ViewCriteria vc=vcm.getViewCriteria("ValidationMessagesVOCriteria1");
            vo.setbMessageKey(msgKey);
            vo.applyViewCriteria(vc, false);
            vo.executeQuery();
            RowSetIterator it=vo.createRowSetIterator(null);
            while(it.hasNext()){
                ValidationMessagesVORowImpl row=(ValidationMessagesVORowImpl) it.next();
                if(row.getMessageLocale().equals(defaultLocale.toLanguageTag())){
                      defaultMessage=row.getMessageText();
                    }
                if(row.getMessageLocale().equals(localString)){
                        localMessage=row.getMessageText();
                        break;
                    }
                }
            it.closeRowSetIterator();
            if(localMessage==null){
                return defaultMessage;
                }
            return localMessage;
        }





In the sample application i have enabled a required and pattern constraint as shown in the following screenshots for dummy entity object’s attribute.





runtimevalidation_defineruntimevalidation_define_messagesruntimevalidation_define_1


runtimevalidationerror   





The application source and sql script can be downloaded from the below mentioned link.


Source Code





Note: If you want to test the application you must create tables under user “raman”.





Edit: To skip validation for existing attribute that has not been modified you can use the following snippet.


                                       Object attribValue=getAttribute(attrName);
                                       int attribIndex=getAttributeIndexOf(attrName);
                                       Object oldAttribValue=getPostedAttribute(attribIndex);
                                       if(attribValue==null || !attribValue.equals(oldAttribValue)){
                                       ArrayList<JboException> list=new ArrayList<JboException>(2);
                                       if(attribRequired.equalsIgnoreCase("Y"))
                                       {
                                       ....................

Posted on Tuesday, January 21, 2014 by raman nanda

Jan 11, 2014

In the previous posts i have covered the API usage and configuration for fortress and a sample login process. In this post i will give an example of how to write your custom ELResolver to check for permission or roles.

The following snippet contains codes for custom ELResolver and helper classes used by it. The following class checks for permission and roles.

 

public class FortressSecurityResolver extends ELResolver {
public static final ADFLogger FortressRoleResolver = ADFLogger.createADFLogger(FortressSecurityResolver.class);


public FortressSecurityResolver() {
super();
}

@Override
public Object getValue(ELContext elContext, Object base,
Object property) {
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
if(property!=null && property instanceof String && ((String)property).startsWith("Fortress")){
elContext.setPropertyResolved(true);
return new PropertyEvaluator((String)property);
}
else if(base instanceof PropertyEvaluator){
String origProperty=((PropertyEvaluator)base).getProperty();
FacesContext context = FacesContext.getCurrentInstance();
if(context!=null){
HttpSession obj=(HttpSession) context.getExternalContext().getSession(false);
HttpSession session=obj;
//this was already stored during login process
if(session.getAttribute("RBACSESSION")!=null){
if(property!=null){
elContext.setPropertyResolved(true);
}
if(origProperty.equals("FortressUserInRole")){
return FortressSecurityController.isUserInRole((String)property,(Session)session.getAttribute("RBACSESSION"));
}
if(origProperty.equals("FortressAllowed")){
Map map=(Map)session.getAttribute("FortressPermissionMap");
if(map==null){
map=FortressSecurityController.getPermissions((Session)session.getAttribute("RBACSESSION"));
session.setAttribute("FortressPermissionMap", map);
}
return map.get(property);
}

}
}
}
return null;
}

@Override
public Class<?> getType(ELContext elContext,Object base,
Object property) {

return Object.class;
}

@Override
public void setValue(ELContext elContext, Object base,
Object property, Object value) {
}

@Override
public boolean isReadOnly(ELContext elContext, Object base,
Object property) {
return false;
}

@Override
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext elContext,
Object base) {
if (base != null) return null;
ArrayList<FeatureDescriptor> list = new ArrayList<FeatureDescriptor>(14);
list.add(Util.getFeatureDescriptor(
"FortressUserInRole",
"FortressUserInRole",
"Checks whether user is in role",
Boolean.FALSE,
Boolean.FALSE,
Boolean.TRUE,
Boolean.class,
Boolean.FALSE)
);
list.add(Util.getFeatureDescriptor(
"FortressAllowed",
"FortressAllowed",
"Checks whether user has the required permission",
Boolean.FALSE,
Boolean.FALSE,
Boolean.TRUE,
Boolean.class,
Boolean.FALSE)
);
return list.iterator();


}

@Override
public Class<?> getCommonPropertyType(ELContext elContext, Object object) {
return null;
}
}


The propertyevaluator class.



public class PropertyEvaluator {
private String property;
private PropertyEvaluator parent;
public PropertyEvaluator(String propertyName) {
this(null,propertyName);
}
public PropertyEvaluator(PropertyEvaluator base,String propertyName) {
this.property=propertyName;
this.parent=base;
}

public void setProperty(String property) {
this.property = property;
}

public String getProperty() {
return property;
}

public void setParent(PropertyEvaluator parent) {
this.parent = parent;
}

public PropertyEvaluator getParent() {
return parent;
}
}


The SecurityController utility class.



public class FortressSecurityController {
public static final ADFLogger FortressSecurityControllerLogger = ADFLogger.createADFLogger(FortressSecurityController.class);
public FortressSecurityController() {
super();
}
/**
* To check whether user is in role
* @return
*/
public static boolean isUserInRole(String roleName,Session rbacSession){
boolean userInRole=false;
List<UserRole> roles=rbacSession.getRoles();
List<UserAdminRole> adminRoles=rbacSession.getAdminRoles();
if(roles!=null&&!roles.isEmpty()){
Iterator it=roles.iterator();
while(it.hasNext()){
UserRole role=(UserRole)it.next();
if(role.getName().equals(roleName)){
return true;
}
}
}
if(adminRoles!=null&&!adminRoles.isEmpty()){
Iterator it=adminRoles.iterator();
while(it.hasNext()){
UserAdminRole role=(UserAdminRole)it.next();
if(role.getName().equals(roleName)){
return true;
}
}
}
return userInRole;
}
/**
* This method returns users permissions in a map
* @return
*/
public static Map getPermissions(Session rbacSession){
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();

Map permsMap=new HashMap();
LDAPOperations ops=new LDAPOperations();
AccessMgr mgr=ops.createAndGetAccessMgr();
DelAccessMgr delMgr=ops.createAndGetDelAccessMgr();

try {
List<Permission> perms=delMgr.sessionPermissions(rbacSession);
if(perms!=null){
Iterator it=perms.iterator();
while(it.hasNext()){
Permission permn=(Permission) it.next();
String mapKey=permn.getObjectName()+":"+permn.getOpName()+":Admin";
permsMap.put(mapKey, true);
}

}
} catch (SecurityException e) {
FortressSecurityControllerLogger.fine("[" + methodName + "]" + "The user ["+rbacSession.getUserId()+"] does not have access",
e);
}

try {
List<Permission> perms=mgr.sessionPermissions(rbacSession);
if(perms!=null){
Iterator it=perms.iterator();
while(it.hasNext()){
Permission permn=(Permission) it.next();
String mapKey=permn.getObjectName()+":"+permn.getOpName()+":Normal";
permsMap.put(mapKey, true);
}

}
} catch (SecurityException e) {
FortressSecurityControllerLogger.fine("[" + methodName + "]" + "The user ["+rbacSession.getUserId()+"] does not have access",
e);
}

return permsMap;
}
}


Now to check for permissions or roles all you have to do is write expressions such as the ones mentioned below in your fragment or jsf page.



//check for permission



#{FortressAllowed[‘taskflowId:permnName:Admin']}



or



#{FortressAllowed[‘taskflowId:permnName:Normal]}



and this is not limited to taskflowId you can create any sort of permission object and corresponding permission you like.



 



What’s next ? Maybe a post on custom authentication realm for weblogic and glassfish for fortress.



 



Note: Application should in general be driven by permissions rather than roles because permissions don’t generally change but roles can be added or removed. So if your application is based on permission it will tend to change less because you can assign or deassign roles the same permission.



If you would like to see a live example PM me and i will provide you with details.

Posted on Saturday, January 11, 2014 by raman nanda

In the previous post i discussed about fortress and its directory structure. In this post i will cover the configuration for securing ADF application, using fortress API, writing your own custom ELResolver for doing permission or role checks.

 

Configuration: Fortress uses a properties file fortress.properties for storing configuration that it uses at runtime to communicate with the openldap server. It also uses ehcache.xml file for caching the retrieved entities from the openldap server to boost performance. Both these files need to be present in your classpath. The sample file structure for fortress.properties and ehcache.xml is shown below.

# Host name and port of LDAP DIT:
host=localhost
port=389

# These credentials are used for read/write access to all nodes under suffix:
admin.user=cn=Manager,dc=sample,dc=com
# LDAP admin root pass is encrypted using 'encrypt' target in build.xml:
admin.pw=W7T0G9hyr344K+DF8gfgA==

# This is min/max settings for LDAP administrator pool connections that have read/write access to all nodes under suffix:
min.admin.conn=10
max.admin.conn=100

# This node contains fortress properties stored on behalf of connecting LDAP clients:
#the config.realm is a node stored in ou=config
config.realm=DEFAULT
config.root=ou=Config,dc=sample,dc=com

# enable this to see trace statements when connection pool allocates new connections:
debug.ldap.pool=true

# Default for pool reconnect flag is false:
enable.pool.reconnect=true

crypto.prop=uiote12434

ehcache.config.file=ehcache.xml


<?xml version="1.0" encoding="UTF-8"?>

<!--
Fortress CacheManager Configuration
==========================
This ehcache.xml corresponds to a single CacheManager.
-->
<ehcache name="fortress-realm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="ehcache.xsd"
updateCheck="true" monitoring="autodetect"
dynamicConfig="true"
>

<cacheManagerEventListenerFactory class="" properties=""/>

<!--
Default Cache configuration. These settings will be applied to caches
created programmatically using CacheManager.add(String cacheName).
This element is optional, and using CacheManager.add(String cacheName) when
its not present will throw CacheException

The defaultCache has an implicit name "default" which is a reserved cache name.
-->
<defaultCache
eternal="false"
overflowToDisk="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
diskSpoolBufferSizeMB="30"
maxElementsOnDisk="10"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>

<!--
Thic cache contains password policy entries. It is used to save a read on User password policy edits. There should be one element for every tenant.
-->
<cache name="fortress.policies"
maxElementsInMemory="10"
maxElementsOnDisk="10"
eternal="false"
overflowToDisk="false"
diskSpoolBufferSizeMB="2"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
/>

<!--
Contains the value OrgUnits for User and Permissions. There should be two elements for every tenant.
-->
<cache name="fortress.ous"
maxElementsInMemory="2"
maxElementsOnDisk="2"
eternal="false"
overflowToDisk="false"
diskSpoolBufferSizeMB="2"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
/>

<!--
Contains the JGraphT hierarchies for RBAC roles. There should be one element for every tenant.
-->
<cache name="fortress.roles"
maxElementsInMemory="10"
maxElementsOnDisk="10"
eternal="false"
overflowToDisk="false"
diskSpoolBufferSizeMB="2"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
/>

<!--
Contains the JGraphT hierarchies for ARBAC roles. There should be one element for every tenant.
-->
<cache name="fortress.admin.roles"
maxElementsInMemory="10"
maxElementsOnDisk="10"
eternal="false"
overflowToDisk="false"
diskSpoolBufferSizeMB="2"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
/>

<!--
Contains the JGraphT hierarchies for Perm OUs. There should be one element for every tenant.
-->
<cache name="fortress.pso"
maxElementsInMemory="10"
maxElementsOnDisk="10"
eternal="false"
overflowToDisk="false"
diskSpoolBufferSizeMB="2"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
/>

<!--
Contains the JGraphT hierarchies for User OUs. There should be one element for every tenant.
-->
<cache name="fortress.uso"
maxElementsInMemory="10"
maxElementsOnDisk="10"
eternal="false"
overflowToDisk="false"
diskSpoolBufferSizeMB="2"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
/>

<!--
Searchable cache contains Role<->DSD mapping. This configuration sets a fairly long TTL of 1 hour.
-->
<cache name="fortress.dsd"
maxElementsInMemory="1000"
maxElementsOnDisk="10"
eternal="false"
overflowToDisk="false"
diskSpoolBufferSizeMB="20"
timeToIdleSeconds="3600"
timeToLiveSeconds="3600"
memoryStoreEvictionPolicy="LFU">
<searchable>
<searchAttribute name="member" expression="value.getMember()"/>
<searchAttribute name="name" expression="value.getName()"/>
<searchAttribute name="contextId" expression="value.getContextId()"/>
</searchable>
</cache>

<!--
Cache contains Role<->SSD mapping.
-->
<cache name="fortress.ssd"
maxElementsInMemory="1000"
maxElementsOnDisk="10"
eternal="false"
overflowToDisk="false"
diskSpoolBufferSizeMB="20"
timeToIdleSeconds="600"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LFU"
/>

</ehcache>


 



Library Dependencies:



You will need the following jars in your classpath which can be added as maven dependencies.



fortress-RC34.jar



ehcache-core.jar (version 2.6.0)



jasypt.jar (version 1.8)



commons-lang (version 2.6)



commons-configuration (version 1.10)



jgrapht-jdk (version 0.7.3)



slf4j-api



unboundid-ldapsdk-2.3.5.jar



 



This basically finishes your configuration.



 



Brief overview of fortress java API:- Though the java docs link i provided in the previous post should be apt. I will just provide a brief overview of the major API classes or interfaces.



 




  1. AdminMgr: Use the implementation of this class for managing users, application roles management, application permission managment.


  2. DelAdminMgr: This is used to perform action on ARBAC entities. You use this for organization creation, admin roles and permission management.


  3. ReviewMgr: Use this for seaching existing users, roles, permissions or permission objects.


  4. DelReviewMgr: Use this for searching organizations, searching roles.


  5. PwPolicyMgr: this can be used for managing password policies for user entities.


  6. AccessMgr: this is used to authenticate user and check for his/her access to a particular permission or role.


  7. DelAccessMgr: this can be used to check for applicable admin permissions and admin roles.



To instantiate each of the objects you can use the corresponding factory class. For example to get a AdminMgr implementation you use the following method.



    private AdminMgr createAndGetAdminMgr() {
String methodName = Thread.currentThread().getStackTrace()[1].getMethodName();
AdminMgr mgr = null;
try {
mgr = AdminMgrFactory.createInstance(GlobalIds.HOME);
} catch (SecurityException e) {
logger.severe("[" + methodName + "]" + "Unable to create AdminMgr Instance", e);
throw new RuntimeException("Unable to create AdminMgr Instance", e
);
}
return mgr;
}


After you have obtained the instance you can perform the corresponding operations on it.



 



Securing your application:



Although fortress comes with its security realm for tomcat or jboss. If you’d like to customize the login process you can do so using its API.



 



Login Process:



In your login method of the servlet or managed bean you use the following method to authenticate and create user session.



Session rbacSession= acMgr.createSession(user, false);


 



This method will validate the user’s password and temporal constraints. It will throw exception in case the user’s password is expired and warning if the user’s password is about to expire and user is using grace login. the way to handle these scenarios would be that if a user’s password is about to expire display this information to him/her by providing option to changePassword or continue to the application.



 



login_flow



The following method is a sample for the login process:



    public String doLogin() {
String un = (String)userName.getValue();
char[] pw = ((String)password.getValue()).toCharArray();
//my custom utility class that contains utility methods for fortress
LDAPOperations ops=new LDAPOperations();
AccessMgr acMgr=ops.createAndGetAccessMgr();
Map pfsScope=ADFContext.getCurrent().getPageFlowScope();
FacesContext context = FacesContext.getCurrentInstance();
HttpSession session=(HttpSession)context.getExternalContext().getSession(true);
//check whether RBACSESSION already exists if so redirect the user to the home page
if(session.getAttribute("RBACSESSION")!=null){
return "success";
}
User user=new User();
user.setUserId(un);
user.setPassword(pw);
try {
Session rbacSession= acMgr.createSession(user, false);
if(rbacSession!=null)
{
//check for warnings if they contain password expiration warning warn him of the same
//and provide option to change the password
List<Warning> warnings=rbacSession.getWarnings();
if(warnings!=null && !warnings.isEmpty())
{
Iterator <Warning> it=warnings.iterator();
while(it.hasNext()){
Warning warning=it.next();
if(warning.getId()==GlobalPwMsgIds.PASSWORD_EXPIRATION_WARNING){
pfsScope.put("errorMessage",warning.getMsg());
session.setAttribute("RBACSESSION", rbacSession);
ExtendedRenderKitService erks =
Service.getRenderKitService(context, ExtendedRenderKitService.class);
//show popup
erks.addScript(context,"AdfPage.PAGE.findComponent('"+popUp.getClientId()+"').show();");
pfsScope.put("expireWarning",Boolean.TRUE);
return "";
}
}
}
session.setAttribute("RBACSESSION", rbacSession);
context.responseComplete();
return "success";
}

} catch (PasswordException e) {
//this means the user's password was reset by admin and hence redirect user to change his password afterward you can log him out
if(e.getErrorId()==GlobalErrIds.USER_PW_RESET){
if(session!=null){
//temporary user id and user's pw policy which can be shown on the change password page
session.setAttribute("userIdTemp", un);
List<User> users=new ArrayList();
users=ops.searchUsers(un);
String pwPol=users.get(0).getPwPolicy();
session.setAttribute("pwPol", pwPol);
context.responseComplete();
return "changePassword";
}
} else{
loginLogger.fine("Login Failed due to authentication failure"+e.getMessage(),e);
pfsScope.put("errorMessage","Login Failed due to authentication failure"+e.getMessage());
}

}
catch (ValidationException e) {
loginLogger.severe("User is not allowed access"+e.getMessage(),e);
pfsScope.put("errorMessage","User is not allowed access"+e.getMessage());
}
catch (FinderException e) {
loginLogger.severe("User does not exist"+e.getMessage(),e);
pfsScope.put("errorMessage","User does not exist in the system");
}
catch (SecurityException e) {
loginLogger.fine("Authentication failed due to"+e.getMessage(),e);
pfsScope.put("errorMessage","System Error Occured"+e.getMessage());
}
ExtendedRenderKitService erks =
Service.getRenderKitService(context, ExtendedRenderKitService.class);
//show popup
erks.addScript(context,"AdfPage.PAGE.findComponent('"+popUp.getClientId()+"').show();");
return "";
}


Here the utility class LDAPOperations is a custom class written by me which in turn uses fortress API’s to access the information.



 



Checking permissions and roles:



 



To perform runtime role or permission checks you can use the following methods.



    public static boolean isUserInRole(String roleName,Session rbacSession){
boolean userInRole=false;
List<UserRole> roles=rbacSession.getRoles();
List<UserAdminRole> adminRoles=rbacSession.getAdminRoles();
//check both in admin and normal roles
if(roles!=null&&!roles.isEmpty()){
Iterator it=roles.iterator();
while(it.hasNext()){
UserRole role=(UserRole)it.next();
if(role.getName().equals(roleName)){
return true;
}
}
}
if(adminRoles!=null&&!adminRoles.isEmpty()){
Iterator it=adminRoles.iterator();
while(it.hasNext()){
UserAdminRole role=(UserAdminRole)it.next();
if(role.getName().equals(roleName)){
return true;
}
}
}
return userInRole;
}

To check for permissions:-

If its a admin permission you use DelAccessMgr for a normal permission you use a AccessMgr instance.



 



DelAccessMgr delMgr=ops.createAndGetDelAccessMgr();
Permission permn=new Permission();
permn.setAdmin(true);
//This is a top level object under which individual permissions are stored
permn.setObjectName("taskflowId");
permn.setOpName("view");
//returns true if user has access to the permission else returns false.
delMgr.checkAccess(rbacSession, permn);


 



That is all fine but then one may ask ADF security provides me with el methods such as isUserInRole and SecurityContext.taskflowViewable etc. To accomplish the same here you can implement your own custom ELResolver.



I will cover the same in the next post.

Posted on Saturday, January 11, 2014 by raman nanda

In this post i will discuss a security solution that one can use to secure their ADF essentials or ADF application. The solution to secure the application utilizes OpenLDAP and fortress. Fortress provides both RBAC(Role based access control) and ARBAC(Administrative role based access control) and OpenLDAP serves as a LDAP directory. Fortress also comes with a set of easy to use API’s through which you can manage permissions, users, roles, organizations,SOD (segregation of duties) policies both dynamic and static, temporal constraints etc, so all in all its a complete open source identity and access management solution and if you don’t want to go through the trouble of using the API’s and implementing your own solution, you can use the enmasse policy server or commander application that comes with the download to manage these things for you.

The added advantage here is that it includes policy and permission enforcement for which, if you were using ADF security, you’d have to use OES and some other servers and integrate them with your application.

 

Basic directory structure :-

A fortress domain includes majorly five organizational units as mentioned below:-

  1. ou=ARBAC: This organizational unit further contains admin permissions, admin roles, permission organizations and user organizations.
  2. ou=RBAC: This organizational unit further contains user’s and roles’ constraints, applicable SOD policies , normal permissions and application roles.
  3. ou=Policies: This node contains the password policies which can be assigned to the user.
  4. ou=config: This node contains the configuration information which at runtime will be used to determine the directory information by fortress.
  5. ou=people: This is the default organizational unit where the users are created.

fortress_directory_structure

I will leave this post here with the links and references where you can read more about fortress and install it. In the next post i will cover how to configure and use fortress with a ADF application.

 

References:

  1. Fortress Java  API : Fortress JAVA API Doc
  2. What is RBAC ? : RBAC
  3. Fortress Downloads : Downloads

Posted on Saturday, January 11, 2014 by raman nanda