/*
* Policy.java
*
* Created on 2008-07-08 07:57
*/
package com.systemtier.jacc;
import com.sun.enterprise.security.auth.login.PasswordCredential;
import com.sun.enterprise.security.provider.PolicyWrapper;
import java.io.IOException;
import java.security.AccessController;
import java.security.Permission;
import java.security.ProtectionDomain;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Hashtable;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.security.auth.Subject;
import javax.sql.DataSource;
/**
* Represents application server security policy. Only permissions instances of
* com.systemtier.jacc.BasicPermission are checked here. Checking of other permissions are passed to
* standard GlassFish policy implementation.
*
* @author Aleksandras Novikovas
* @author System Tier
* @version 1.0
*/
public class Policy
extends PolicyWrapper {
/**
* Holds full class name. Mostly used for logging.
*/
private static final String CLASS_NAME = Policy.class.getName ();
/**
* The logger object.
*/
private static final Logger log = Logger.getLogger (CLASS_NAME);
/**
* Holds JDBC resource name to access security database.
*/
public static String JDBC_RESOURCE_NAME = "jdbc/authPool";
/**
* Query used to test BasicPermission for the user.
*/
// PostgreSql
public static String PERMISSION_TEST_QUERY = "select exists (select 1 from sec.active_user_list au join sec.group_user gu on gu.user_name = au.user_name join sec.group_permission gp on gu.group_name = gp.group_name where au.user_name = ? and gp.permission_name = ?)";
// MS SQL
// public static String PERMISSION_TEST_QUERY = "select case when exists (select 1 from sec.active_user_list au join sec.group_user gu on gu.user_name = au.user_name join sec.group_permission gp on gu.group_name = gp.group_name where au.user_name = ? and gp.permission_name = ?) then 1 else 0 end";
/**
* Holds cache for tested permissions.
*/
private Hashtable> cache;
/**
* Creates a new instance of Policy.
*/
public Policy () {
super ();
cache = new Hashtable> ();
Properties settings = new Properties ();
try {
if (log.isLoggable (Level.FINEST)) {
log.finest ("Reading settings file.");
}
settings.load (getClass ().getClassLoader ().getResourceAsStream ("com/systemtier/jacc/settings.properties"));
p = settings.getProperty ("JDBC_RESOURCE_NAME");
if (p != null) {
JDBC_RESOURCE_NAME = p;
if (log.isLoggable (Level.FINEST)) {
log.finest ("JDBC_RESOURCE_NAME=" + JDBC_RESOURCE_NAME);
}
}
p = settings.getProperty ("PERMISSION_TEST_QUERY");
if (p != null) {
PERMISSION_TEST_QUERY = p;
if (log.isLoggable (Level.FINEST)) {
log.finest ("PERMISSION_TEST_QUERY=" + PERMISSION_TEST_QUERY);
}
}
}
catch (IOException ex) {
if (log.isLoggable (Level.WARNING)) {
log.log (Level.WARNING, "Settings for properties can not be openned. Using defaults.");
}
}
if (log.isLoggable (Level.INFO)) {
log.log (Level.INFO, CLASS_NAME + " instantiated.");
}
}
@Override
public void refresh () {
if (log.isLoggable (Level.FINER)) {
log.entering (CLASS_NAME, "refresh");
}
cache.clear ();
super.refresh ();
if (log.isLoggable (Level.FINER)) {
log.exiting (CLASS_NAME, "refresh");
}
}
/**
* Evaluates the global policy for the permissions granted to the ProtectionDomain and tests whether
* the permission is granted.
* Only com.systemtier.jacc.BasicPermission instances are checked in this method. Checking of other
* permissions are passed to standard GlassFish policy implementation.
* Checking is done by querying database.
*
* @param domain ProtectionDomain to test.
* @param permission Permission object to be tested for implication.
*
* @return boolean true if permission is a proper subset of a permission granted to this
* ProtectionDomain.
*/
@Override
public boolean implies (ProtectionDomain domain, Permission permission) {
if (permission instanceof BasicPermission) {
if (log.isLoggable (Level.FINER)) {
log.entering (CLASS_NAME, "implies", new Object [] {permission});//domain, permission
}
if (log.isLoggable (Level.FINEST)) {
log.finest ("Permission is instanceof BasicPermission.");
}
String userName = getCurrentUserName ();
if (userName != null) {
if (log.isLoggable (Level.FINEST)) {
log.finest ("Retrieving permission test result from cache.");
}
// Checking cache before doing actual test of permission
Hashtable cachedSubjectPermissions = cache.get (userName);
if (cachedSubjectPermissions == null) {
// Create permissions cache for subject.
cachedSubjectPermissions = new Hashtable ();
cache.put (userName, cachedSubjectPermissions);
}
// Retrieving cached permission test.
Boolean cachedPermissionResult = cachedSubjectPermissions.get (permission);
if (cachedPermissionResult == null) {
Context ctx = null;
DataSource ds = null;
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
if (log.isLoggable (Level.FINEST)) {
log.finest ("Retrieving permission test result from database.");
}
ctx = new InitialContext ();
ds = (DataSource) ctx.lookup (JDBC_RESOURCE_NAME);
con = ds.getConnection ();
ps = con.prepareStatement (PERMISSION_TEST_QUERY);
ps.setString (1, userName);
ps.setString (2, permission.getName ());
if (log.isLoggable (Level.FINEST)) {
log.finest ("Executing database request to test permission.");
}
rs = ps.executeQuery ();
boolean authorized = false;
if (rs.next ()) authorized = rs.getBoolean (1);
if (log.isLoggable (Level.FINEST)) {
log.finest ("Caching permission test result.");
}
cachedSubjectPermissions.put (permission, new Boolean (authorized));
if (log.isLoggable (Level.FINER)) {
log.exiting (CLASS_NAME, "implies", authorized?Boolean.TRUE:Boolean.FALSE);
}
return authorized;
}
catch (Exception ex) {
log.log (Level.SEVERE, "Permission can not be tested.", ex);
}
finally {
if (rs != null) {
try {
rs.close ();
}
catch (Exception ex) {}
}
if (ps != null) {
try {
ps.close ();
}
catch (Exception ex) {}
}
if (con != null) {
try {
con.close ();
}
catch (Exception ex) {}
}
if (ctx != null) {
try {
ctx.close ();
}
catch (Exception ex) {}
}
}
}
else {
if (log.isLoggable (Level.FINEST)) {
log.finest ("Permission test result found in cache.");
}
if (log.isLoggable (Level.FINER)) {
log.exiting (CLASS_NAME, "implies", cachedPermissionResult);
}
return cachedPermissionResult.booleanValue ();
}
}
if (log.isLoggable (Level.FINER)) {
log.exiting (CLASS_NAME, "implies", Boolean.FALSE);
}
return false;
}
else {
return super.implies (domain, permission);
}
}
/**
* Returns current subject's user name. Subject is taken from current calling context.
* User name is extracted from first found private PasswordCredential.
* Returns null if subject can not be retrieved or it does not contain private
* PasswordCredential or user name in PasswordCredential is not set.
*
* @return String current user's name or null.
*/
public static String getCurrentUserName () {
if (log.isLoggable (Level.FINER)) {
log.entering (CLASS_NAME, "getCurrentUserName");
}
String userName = null;
Subject subject = Subject.getSubject (AccessController.getContext ());
if (subject != null) {
if (log.isLoggable (Level.FINEST)) {
log.finest ("Got subject: " + subject.toString ());
}
Set