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.
- AdminMgr: Use the implementation of this class for managing users, application roles management, application permission managment.
- DelAdminMgr: This is used to perform action on ARBAC entities. You use this for organization creation, admin roles and permission management.
- ReviewMgr: Use this for seaching existing users, roles, permissions or permission objects.
- DelReviewMgr: Use this for searching organizations, searching roles.
- PwPolicyMgr: this can be used for managing password policies for user entities.
- AccessMgr: this is used to authenticate user and check for his/her access to a particular permission or role.
- 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.
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.