package com.venky.swf.plugins.security.extensions;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.venky.cache.Cache;
import com.venky.core.log.SWFLogger;
import com.venky.core.log.TimerStatistics.Timer;
import com.venky.core.string.StringUtil;
import com.venky.core.util.ObjectUtil;
import com.venky.extension.Extension;
import com.venky.extension.Registry;
import com.venky.swf.db.Database;
import com.venky.swf.db.model.Model;
import com.venky.swf.db.model.User;
import com.venky.swf.db.model.reflection.ModelReflector;
import com.venky.swf.db.table.BindVariable;
import com.venky.swf.db.table.Table;
import com.venky.swf.exceptions.AccessDeniedException;
import com.venky.swf.path.Path;
import com.venky.swf.plugins.security.db.model.Role;
import com.venky.swf.plugins.security.db.model.RolePermission;
import com.venky.swf.plugins.security.db.model.UserRole;
import com.venky.swf.pm.DataSecurityFilter;
import com.venky.swf.routing.Config;
import com.venky.swf.sql.Conjunction;
import com.venky.swf.sql.Expression;
import com.venky.swf.sql.Operator;
import com.venky.swf.sql.Select;
import com.venky.swf.sql.parser.SQLExpressionParser;
import com.venky.swf.sql.parser.XMLExpressionParser;
public class ParticipantControllerAccessExtension implements Extension{
private static ParticipantControllerAccessExtension instance = null;
static {
instance = new ParticipantControllerAccessExtension();
Registry.instance().registerExtension(Path.ALLOW_CONTROLLER_ACTION, instance);
Registry.instance().registerExtension(RolePermission.class.getSimpleName() + ".after.save" , instance.permissionCacheBuster);
Registry.instance().registerExtension(RolePermission.class.getSimpleName() + ".after.destroy", instance.permissionCacheBuster);
Registry.instance().registerExtension(UserRole.class.getSimpleName() + ".after.save" , instance.permissionCacheBuster);
Registry.instance().registerExtension(UserRole.class.getSimpleName() + ".after.destroy", instance.permissionCacheBuster); }
private class PermissionCacheBuster implements Extension {
@Override
public void invoke(Object... context) {
synchronized (permissionCache) {
for (String key :permissionCache.keySet()){
permissionCache.get(key).clear();
}
permissionCache.clear();
}
}
}
private PermissionCacheBuster permissionCacheBuster = new PermissionCacheBuster();
private final SWFLogger cat = Config.instance().getLogger(getClass().getName());
public void invoke(Object... context) {
Timer timer = cat.startTimer("Participant Controller Action invoke");
try {
_invoke(context);
}finally {
timer.stop();
}
}
private boolean isControllerActionAccessibleAtAll(final User user, final String controllerPathElementName, final String actionPathElementName,final Path path){
String transactionKey = getClass().getName()+".isControllerActionAccessibleAtAll";
Cache<String,Cache<String,Boolean>> cache = Database.getInstance().getCurrentTransaction().getAttribute(transactionKey);
if (cache == null){
cache = new Cache<String, Cache<String,Boolean>>() {
private static final long serialVersionUID = 998528782452357935L;
@Override
protected Cache<String, Boolean> getValue(final String controllerPathElementName) {
return new Cache<String, Boolean>() {
private static final long serialVersionUID = 1897514771224474367L;
@Override
protected Boolean getValue(final String actionPathElementName) {
return isControllerActionAccessible(user,controllerPathElementName, actionPathElementName, null, path);
}
};
}
};
Database.getInstance().getCurrentTransaction().setAttribute(transactionKey,cache);
}
return cache.get(controllerPathElementName).get(actionPathElementName);
}
private boolean isControllerActionAccessible(final User user, final String controllerPathElementName, final String actionPathElementName,
final String parameterValue,
Path path){
Timer timer = cat.startTimer("Check If Action is Secured");
boolean securedAction = path.isActionSecure(actionPathElementName);
timer.stop();
if (!securedAction){
return true;
}else if (user == null){
return false;
}
Class<? extends Model> modelClass = null;
Set<String> participantingRoles = new HashSet<String>();
Model selectedModel = null;
Table<? extends Model> possibleTable = Path.getTable(controllerPathElementName);
if ( possibleTable != null ){
modelClass = possibleTable.getModelClass();
}
Timer gettingParticipatingRoles = cat.startTimer("Getting participating Roles");
if (modelClass != null ){
Timer t = cat.startTimer("Getting model Reflector", Config.instance().isTimerAdditive());
ModelReflector<? extends Model> ref = ModelReflector.instance(modelClass);
t.stop();
if (parameterValue != null){
t = cat.startTimer("Getting Participating Roles when parameter != null", Config.instance().isTimerAdditive());
try {
int id = Integer.valueOf(parameterValue);
selectedModel = possibleTable.get(id);
if (selectedModel != null){
participantingRoles = selectedModel.getParticipatingRoles(user);
}
}catch (NumberFormatException ex){
participantingRoles = ref.getParticipatableRoles();
}catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}finally {
t.stop();
}
}else {
t = cat.startTimer("Getting Participating Roles when parameter == null", Config.instance().isTimerAdditive());
participantingRoles = ref.getParticipatableRoles() ;
t.stop();
}
}
gettingParticipatingRoles.stop();
Timer preparingPermissionQuery = cat.startTimer("Preparing Permission query");
ModelReflector<RolePermission> permissionRef = ModelReflector.instance(RolePermission.class);
Expression permissionQueryWhere = new Expression(permissionRef.getPool(),Conjunction.AND);
Expression participationWhere = new Expression(permissionRef.getPool(),Conjunction.OR);
participationWhere.add(new Expression(permissionRef.getPool(),"participation",Operator.EQ));
for (String participatingRole:participantingRoles){
participationWhere.add(new Expression(permissionRef.getPool(),"participation",Operator.EQ,new BindVariable(permissionRef.getPool(),participatingRole)));
}
permissionQueryWhere.add(participationWhere);
boolean defaultController = false;
if (ObjectUtil.isVoid(controllerPathElementName)){
defaultController = true;
}
Expression controllerActionWhere = new Expression(permissionRef.getPool(),Conjunction.OR);
controllerActionWhere.add(new Expression(permissionRef.getPool(),Conjunction.AND).add(new Expression(permissionRef.getPool(),"controller_path_element_name",Operator.EQ))
.add(new Expression(permissionRef.getPool(),"action_path_element_name",Operator.EQ)));
if (defaultController){
controllerActionWhere.add(new Expression(permissionRef.getPool(),Conjunction.AND).add(new Expression(permissionRef.getPool(),"controller_path_element_name",Operator.EQ))
.add(new Expression(permissionRef.getPool(),"action_path_element_name",Operator.EQ)));
}else {
controllerActionWhere.add(new Expression(permissionRef.getPool(),Conjunction.AND).add(new Expression(permissionRef.getPool(),"controller_path_element_name",Operator.EQ,controllerPathElementName))
.add(new Expression(permissionRef.getPool(),"action_path_element_name",Operator.EQ)));
}
if (defaultController){
controllerActionWhere.add(new Expression(permissionRef.getPool(),Conjunction.AND).add(new Expression(permissionRef.getPool(),"controller_path_element_name",Operator.EQ))
.add(new Expression(permissionRef.getPool(),"action_path_element_name",Operator.EQ,new BindVariable(permissionRef.getPool(),actionPathElementName))));
}else {
controllerActionWhere.add(new Expression(permissionRef.getPool(),Conjunction.AND).add(new Expression(permissionRef.getPool(),"controller_path_element_name",Operator.EQ,controllerPathElementName))
.add(new Expression(permissionRef.getPool(),"action_path_element_name",Operator.EQ,new BindVariable(permissionRef.getPool(),actionPathElementName))));
}
permissionQueryWhere.add(controllerActionWhere);
preparingPermissionQuery.stop();
Timer selectingUserRole = cat.startTimer("Selecting user Roles");
Select userRoleQuery = new Select().from(UserRole.class);
userRoleQuery.where(new Expression(userRoleQuery.getPool(),"user_id",Operator.EQ,new BindVariable(userRoleQuery.getPool(),user.getId())));
List<UserRole> userRoles = userRoleQuery.execute(UserRole.class);
selectingUserRole.stop();
Timer preparingRoleWhere = cat.startTimer("Preparing role Where clause");
List<Integer> userRoleIds = new ArrayList<Integer>();
ModelReflector<Role> roleRef = ModelReflector.instance(Role.class);
Expression roleWhere = new Expression(roleRef.getPool(),Conjunction.OR);
roleWhere.add(new Expression(roleRef.getPool(),"role_id",Operator.EQ));
if (!userRoles.isEmpty()){
for (UserRole ur:userRoles){
userRoleIds.add(ur.getRoleId());
}
roleWhere.add(new Expression(userRoleQuery.getPool(),"role_id",Operator.IN,userRoleIds.toArray()));
}
preparingRoleWhere.stop();
permissionQueryWhere.add(roleWhere);
Timer selectingRolePermissions = cat.startTimer("Selecting from role permissions");
Select permissionQuery = new Select().from(RolePermission.class);
permissionQuery.where(permissionQueryWhere);
List<RolePermission> permissions = permissionQuery.execute();
selectingRolePermissions.stop();
if (selectedModel != null){
Timer removingPermissionRecords = cat.startTimer("Remove permission records based on condition.");
for (Iterator<RolePermission> permissionIterator = permissions.iterator(); permissionIterator.hasNext() ; ){
RolePermission permission = permissionIterator.next();
Reader condition = permission.getConditionText();
String sCondition = (condition == null ? null : StringUtil.read(condition));
if (!ObjectUtil.isVoid(sCondition)){
Expression expression = new SQLExpressionParser(modelClass).parse(sCondition);
if (expression == null ){
expression = new XMLExpressionParser(modelClass).parse(sCondition);
}
if (!expression.eval(selectedModel)) {
permissionIterator.remove();
}
}
}
removingPermissionRecords.stop();
}
if (permissions.isEmpty()){
return true ;
}
return permissionCache.isAllowed(permissions, userRoleIds);
}
private PermissionCache permissionCache = new PermissionCache();
private class PermissionCache extends Cache<String,Cache<String,Boolean>> {
private static final long serialVersionUID = 8076958083615092776L;
public boolean isAllowed(List<RolePermission> permissions, List<Integer> userRoleIds){
List<Integer> permissionIds = DataSecurityFilter.getIds(permissions);
List<Integer> copyuserRoleIds = new ArrayList<Integer>(userRoleIds);
Collections.sort(permissionIds);
Collections.sort(copyuserRoleIds);
String userRolesKey = copyuserRoleIds.toString();
String permissionsKey = permissionIds.toString();
Boolean value = get(userRolesKey).get(permissionsKey);
if (value == null){
value = calculatePermission(permissions, userRoleIds);
get(userRolesKey).put(permissionsKey, value);
}
return value;
}
@Override
protected Cache<String, Boolean> getValue(String k) {
return new Cache<String, Boolean>() {
private static final long serialVersionUID = -6669779570540556969L;
@Override
protected Boolean getValue(String k) {
return null;
}
};
}
private boolean calculatePermission(List<RolePermission> permissions, List<Integer> userRoleIds){
Timer sortingPermissions = cat.startTimer("sorting permissions", Config.instance().isTimerAdditive());
Collections.sort(permissions, rolepermissionComparator);
sortingPermissions.stop();
//permissions,userRoleIds,
Timer permissionsChecking = cat.startTimer("Checking Permissions for being allowed");
try {
RolePermission firstPermission = permissions.get(0);
RolePermission currentPermissionGroup = firstPermission;
Iterator<RolePermission> permissionIterator = permissions.iterator();
while (permissionIterator.hasNext()){
RolePermission effective = permissionIterator.next();
if (permissionGroupComparator.compare(currentPermissionGroup,effective) < 0){
if (currentPermissionGroup.getRoleId() != null ){
userRoleIds.remove(effective.getRoleId());
}else {
break;
}
currentPermissionGroup = effective;
}
if (effective.getRoleId() != null && !userRoleIds.contains(effective.getRoleId())){
//Disallowed at more granular level for this role. So Ignore this record.
continue;
}
if (effective.isAllowed()){
if (effective.getRoleId() != null || firstPermission.getRoleId() == null){
return true;
}else if (!userRoleIds.isEmpty() ){
//First role not null but effective.role is null.
//If User has atleast one more role that is not configured as disallowed then allowed.
return true;
}else {
//Role level dissallowed will override.
break;
}
}
}
}finally {
permissionsChecking.stop();
}
return false;
}
private Comparator<RolePermission> permissionGroupComparator = new Comparator<RolePermission>() {
@Override
public int compare(RolePermission o1, RolePermission o2) {
int ret = 0;
if (ret == 0){
ret = StringUtil.valueOf(o2.getControllerPathElementName()).compareTo(StringUtil.valueOf(o1.getControllerPathElementName()));
}
if (ret == 0){
ret = StringUtil.valueOf(o2.getActionPathElementName()).compareTo(StringUtil.valueOf(o1.getActionPathElementName()));
}
if (ret == 0 && o1.getRoleId() != null && o2.getRoleId() != null){
ret = o1.getRoleId().compareTo(o2.getRoleId());
}
return ret;
}
};
private Comparator<RolePermission> rolepermissionComparator = new Comparator<RolePermission>() {
public int compare(RolePermission o1, RolePermission o2) {
int ret = 0;
if (ret == 0){
if (o1.getRoleId() == null && o2.getRoleId() != null){
ret = 1;
}else if (o2.getRoleId() == null && o1.getRoleId() != null){
ret = -1;
}else {
ret = 0;
}
}
if (ret == 0){
ret = permissionGroupComparator.compare(o1, o2);
}
if (ret == 0) {
ret = StringUtil.valueOf(o2.getParticipation()).compareTo(StringUtil.valueOf(o1.getParticipation()));
}
return ret;
}
};
}
public void _invoke(Object... context) {
User user = (User)context[0];
if (user != null && user.isAdmin()){
return;
}
String controllerPathElementName = (String)context[1];
String actionPathElementName = (String)context[2];
String parameterValue = (String)context[3];
Path tmpPath = (Path)context[4];
if (tmpPath == null){
Timer constructPath = cat.startTimer("Create Path");
tmpPath = new Path("/"+controllerPathElementName+"/"+actionPathElementName + (parameterValue == null ? "" : "/"+parameterValue));
constructPath.stop();
}
if (!isControllerActionAccessibleAtAll(user, controllerPathElementName, actionPathElementName, tmpPath)){
//This is a cached Check.
throw new AccessDeniedException(tmpPath.getTarget());
}
if (!isControllerActionAccessible(user, controllerPathElementName, actionPathElementName, parameterValue, tmpPath)){
throw new AccessDeniedException(tmpPath.getTarget());
}
}
}