/*
 * $Id: PermissionManager.java,v 1.26 2009/01/14 15:11:10 korowa Exp $
 *
 * Copyright (c) Siemens AG 2008 
 *           (c) Universität Paderborn 2008 
 * Licensed under EUPL 1.1. 
 *
 * $Log: PermissionManager.java,v $
 * Revision 1.26  2009/01/14 15:11:10  korowa
 * Anpassung der Funktionen zum Anzeigen der Kategorie- und Dokumentlinks. Ab jetzt werden die Links für die 	Guest-Users nur angezeigt, wen auch die entsprechende Rechte(lesen) für "Jeder" auf "true" gesetzt werden.
 *
 * Revision 1.25  2005/03/24 12:57:05  marbobo
 * + Unnötige Anweisungen entfernt
 *
 * Revision 1.24  2005/02/15 09:26:53  bigmac
 * Refactorings
 *
 * Revision 1.23  2005/01/18 12:00:18  jonyp
 * hasAnyOtherPermission: added VKCuser parameter (perliminary!)
 *
 * Revision 1.22  2005/01/17 23:31:49  jonyp
 * added hasUnauthenticatedPermission
 *
 * Revision 1.21  2004/12/09 10:52:23  jonyp
 * code performance in hasLdapGroupPermission, preliminary
 *
 * Revision 1.20  2004/12/08 14:00:54  marbobo
 * + Owner-Rechte nur übernehmen wenn aktiviert
 *
 * Revision 1.19  2004/12/01 16:54:09  jonyp
 * no special lookup for user of instance ldapUser (preliminary)
 *
 * Revision 1.18  2004/11/15 12:35:39  vchris
 * + Cleanup-Job für obsolete LDAP-Group Einträge
 *
 * Revision 1.17  2004/11/10 15:02:50  koppy
 * Projektweite Überarbeitung von Error-Logs, Stacktraces,..
 *
 * Revision 1.16  2004/11/09 22:48:49  vchris
 * hasLdapGroupPermission funktioniert in erster Näherung
 * (experimental!)
 *
 * Revision 1.15  2004/11/09 13:00:33  vchris
 * Erweiterung auf LDAP-Gruppenrechteprüfung:
 * (experimental!)
 *
 * Revision 1.14  2004/11/02 21:40:12  marbobo
 * + Bugfix
 *
 * Revision 1.13  2004/10/31 02:59:12  marbobo
 * + hasPermissionsRecursiv()
 *
 * Revision 1.12  2004/10/24 18:20:03  marbobo
 * + Ulgültige Gruppen bei der Rechteüberprüfung berücksichtigen
 *
 * Revision 1.11  2004/10/22 15:31:50  jonyp
 * added check for ldap user (preliminary)
 *
 * Revision 1.10  2004/07/20 08:38:58  jonyp
 * added copyright info + cvs log
 *
 *
 */

package vkc.security;

import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.naming.NamingEnumeration;
import javax.naming.directory.SearchControls;
import javax.naming.ldap.LdapContext;

import vkc.db.entities.VKCAclEntry;
import vkc.db.entities.VKCAclType;
import vkc.db.entities.VKCCategory;
import vkc.db.entities.VKCGroup;
import vkc.db.entities.VKCPermission;
import vkc.db.entities.VKCSubCategory;
import vkc.db.entities.VKCUser;
import vkc.db.serializer.VKCAclSerializer;
import vkc.db.serializer.VKCAclTypeSerializer;
import vkc.db.serializer.VKCCategorySerializer;
import vkc.db.serializer.VKCGroupSerializer;
import vkc.db.serializer.VKCPermissionSerializer;
import vkc.exception.AclException;
import vkc.exception.DBException;
import vkc.ldap.LdapProxy;
import vkc.ldap.VKCLdapUser;
import vkc.ldap.db.entities.VKCLdapGroup;
import vkc.ldap.db.entities.VKCLdapServer;
import vkc.ldap.db.serializer.VKCLdapGroupSerializer;
import vkc.misc.VKCLogManager;
import vkc.util.CategoryUtils;
import vkc.util.ObjectCloner;

public class PermissionManager {

	private PermissionManager() {
	}

	/**
	 * Überprüfung eines Rechtes
	 *
	 * @param acl VKCAcl
	 * @param user Id des zu überprüfenden Users
	 * @param permToken das Token, auf welches der User überprüft werden soll
	 *
	 * @return true (user hat recht) false (user hat das recht nicht)
	 */
	public static final boolean hasPermission( VKCAcl acl, VKCUser user, char permToken ) {
		if ( user == null || acl == null ) {
			return false;
		}

		if ( !user.isUserValid() ) {
			return false;
		}

		if ( user.hasAdminPrivileges() ) {
			return true;
		}


// forget this if clause here for a while, may be commented in, when KW 50 is passed and
// aclType ldap_user is introduced... jp of 1.12.2004		
		
//		if ( !( user instanceof VKCLdapUser ) ) {
	
			// if permToken is in permMask => deny access
			if ( acl.getMaskPermString().indexOf( permToken ) != -1 ) {
				return false;
			}
//           if anyother user (with credentials, means loggedIn user) has the given permission => grant access
            if ( hasAnyotherPermission( acl, user, permToken ) ) {
                return true;
            }
			if ( acl.hasUserEntry( user ) ) { // if the given user has an entry
				// if the given user has the given permission for this category
				if ( hasUserPermission( acl, user.getUserId(), permToken ) ) {
					// user has an entry and required perm...
					return true;
				}
	
				// user has an entry and NOT required perm...
				return false;
			}
	
			// if the given user is in a group for this category
			if ( acl.hasGroupEntry( user ) ) {
				// if the given user is member of a group which has the given permission for this category
				if ( hasGroupPermission( acl, user, permToken ) ) {
					// user is member of a group and required perm...
					return true;
				} 
	
				// user is member of a group and NOT required perm...
				return false;
			}
//		} else {
		    // user is external user
			if (hasLdapGroupPermission( acl, user, permToken ) ) {
				return true;
			}
//		}

		

		// if unauthenticated user (not loggedIn user, means guestUser) has the given permission => grant access
		if ( hasUnauthenticatedPermission (acl, user, permToken)) {
			return true;
		}
		
        // fall through the ladder: deny access
		return false;
	}

	/**
	 * Überprüfung der Rechte
	 *
	 * @param acl VKCAcl
	 * @param user Id des zu überprüfenden Users
	 * @param String von permToken, auf welches der User überprüft werden soll
	 *
	 * @return true (user hat ALLE rechte) false (user kein recht bzw es fehlt ihm min. eins)	  
	 */
	public static final boolean hasPermissions( VKCAcl acl, VKCUser user, String permTokens ) {
        if ( acl == null || user == null || permTokens == null || permTokens.length() <= 0 ) {
        	return false;
        }
           
        // iterate through all chars of the permToken string and check permission for each token          
        for ( int index = 0; index <= permTokens.length() - 1; index++ ) {
			char permToken = permTokens.charAt( index );
		
			if ( !hasPermission( acl, user, permToken ) ) {
                // no, not that permission ==> give up  
				return false;  
			} 

			continue;
        }
        
		// went through all permissions with no return of "false",
		// so the given user must have all given permissions
		return true;
	}

	/**
	 * Rekursive Überprüfung der Rechte für eine Kategorie
	 *
	 * @return true (user hat ALLE rechte) false (user kein recht bzw es fehlt ihm min. eins)	  
	 * @throws Exception
	 */
	public static final boolean hasPermissionsRecursiv( Integer categoryId, VKCUser user, String permTokens ) throws Exception {
        if ( categoryId == null || user == null || permTokens == null || permTokens.length() <= 0 ) {
        	return false;
        }
        
        if ( !hasPermissions( VKCAclSerializer.getAclByCategoryId( categoryId ), user, permTokens ) ) {
            return false;
        }
        
        Set categoryIds = CategoryUtils.getAllSubCategoryIdsRecursiv( categoryId );
        
        // iterate through all chars of the permToken string and check permission for each token          
        for ( int i = 0; i <= permTokens.length() - 1; i++ ) {
			char permToken = permTokens.charAt( i );
			Set catIds = (Set)ObjectCloner.deepCopy( categoryIds );
		
			Set tmpCatIds = CategoryUtils.getCategoryIds( categoryId, user, permToken );
			tmpCatIds.remove( categoryId );
			catIds.removeAll( tmpCatIds );
			
			if ( !catIds.isEmpty() ) {
			    return false;
			}
			
			continue;
        }
        
		// went through all permissions with no return of "false",
		// so the given user must have all given permissions
		return true;
	}

    public static final String getEffectivePermissions( VKCAcl acl, VKCUser user ) {
    	//TODO: do some coding here folks
    	return "";
    }

	private static final boolean hasUserPermission( VKCAcl acl, Integer userId, char permToken ) {
		VKCAclEntry aclEntries = new VKCAclEntry();

		// iterate through all elements
		for ( Enumeration e = acl.getAclEntries().elements(); e.hasMoreElements(); ) {
			aclEntries = (VKCAclEntry) e.nextElement();

			// look out for only acltypes of user(1)
			if ( aclEntries.getAclType().getAclTypeId().intValue() != VKCAclType.USER_TYPE ) {
				continue;
			}

			// is it the requested user ?
			if ( !aclEntries.getAccessorId().equals( userId ) ) {
				continue;
			}

			// has he the requested permission
			if ( aclEntries.getPermission().getToken() == permToken ) {
				return true;
			}
		}

		return false;
	}

	private static final boolean hasLdapGroupPermission( VKCAcl acl, VKCUser user, char permToken ) {
		// HashSet userGroupIds = new HashSet();
		VKCAclEntry aclEntries = new VKCAclEntry();
		// Set allLdapGroups;
		
		// ein normaler User kann nicht Mitglied einer LDAP-Gruppe sein
		if ( !(user instanceof VKCLdapUser)) return false;
		
		VKCLdapUser ldapUser = (VKCLdapUser)user;
		
		VKCLdapServer ldapServer = null;
		
		try {
			ldapServer = ldapUser.getLdapServer();
		} catch (Exception e) {
			// Falls die LdapServerId nicht geholt werden kann, keine Berechtigung zuweisen
			return false;
		}
		
		
		// Jetzt durch die gesamte ACL iterieren und für jeden LDAP-Group Eintrag 
		for (Enumeration e = acl.getAclEntries().elements(); e.hasMoreElements(); ) {
			aclEntries = (VKCAclEntry) e.nextElement();
			
			// Nur nach LDAP_GROUP_TYPE Entries suchen
			if ( aclEntries.getAclType().getAclTypeId().intValue() != VKCAclType.LDAP_GROUP_TYPE) {
				continue;
			}
			
			// Der LDAP-Gruppeneintrag muss vom selben Server stammen wie der LDAP-User
			VKCLdapGroup ldapGroup = null;
			try {
				ldapGroup = VKCLdapGroupSerializer.retrieveLdapGroup(aclEntries.getAccessorId());
				if ( !ldapGroup.getLdapServerId().equals(ldapServer.getLdapServerId())) {
					continue;
				}
			} catch ( Exception ex ){
				continue;
			}
			
			// Hier die LDAP-Gruppenzugehörigkeits-Anfrage an den LDAP senden.
			LdapContext ldapContext 	= ldapUser.getLdapContext();
			String searchBase 			= ldapGroup.getLdapGroupRDN() + "," + ldapServer.getGroupsBaseRDN() + "," + ldapServer.getBaseDN();
			String searchFilter			= "(" + ldapServer.getMapping_loginattr() + "=" + ldapUser.getUser() + ")";
			try {
				NamingEnumeration namingEnumeration = LdapProxy.search(ldapContext, searchBase, searchFilter, SearchControls.SUBTREE_SCOPE);
				if (namingEnumeration != null) {
					// TODO: Auswertung verändern: Nicht nur auf hasMore prüfen, da dies hier nur "in erster Näherung" geschehen ist
					if (namingEnumeration.hasMore()) {
						// Wenn Elemente enthalten sind, ist der User in der Gruppe.
						// Stimmt nun noch das Token überein, hat der Benutzer die Berechtigung über die LDAP-Gruppe :-)
						if (permToken == aclEntries.getPermission().getToken()) return true;
					}
				}
			} catch (Exception ex) {
				continue;
			}
		}
/*          
            // TODO: test this routine to save redundant code, may be not so performant  
			try {	
				if (ldapUser.isMemberOfLdapGroup(aclEntries.getAccessorId()) && permToken == aclEntries.getPermission().getToken()) {
					// if user is member of the group and permToken is correct
				    return true;
				}
			} catch (Exception ex) {
				continue;
			}
		}
*/		
		return false;
	}

	private static final boolean hasGroupPermission( VKCAcl acl, VKCUser user, char permToken ) {
		HashSet userGroupIds = new HashSet();
		VKCAclEntry aclEntries = new VKCAclEntry();
		Set allGroups = user.getGroups();
		Set validGroupIds = new HashSet();
		
		try {
            validGroupIds = VKCGroupSerializer.getAllValidGroupIds();
        } catch ( DBException e ) {
            VKCLogManager.errorLog(PermissionManager.class, "", e.getMessage(),e);
        }

		Iterator it = allGroups.iterator();

		while ( it.hasNext() ) {
			userGroupIds.add( ( (VKCGroup) it.next() ).getGroupId() );
		}

		// iterate through all entries of the acl
		for ( Enumeration e = acl.getAclEntries().elements(); e.hasMoreElements(); ) {
			aclEntries = (VKCAclEntry) e.nextElement();

			// look out for only acltypes of group(2)
			if ( aclEntries.getAclType().getAclTypeId().intValue() != VKCAclType.GROUP_TYPE ) {
				continue;
			}

			// check, if user is member of the given group
			// => accessorId must be in the hashmap
			if ( !userGroupIds.contains( aclEntries.getAccessorId() ) ) {
				continue;
			}

			// check, if the permission is given
			if ( aclEntries.getPermission().getToken() == permToken && validGroupIds.contains( aclEntries.getAccessorId() ) ) {
				return true;
			}
		}

		return false;
	}

	private static final boolean hasAnyotherPermission( VKCAcl acl, VKCUser user, char permToken ) {
		VKCAclEntry aclEntries = new VKCAclEntry();
/*
		if (!user.isGuestUser()) {
*/		
			// iterate through all entries
			for ( Enumeration e = acl.getAclEntries().elements(); e.hasMoreElements(); ) {
				aclEntries = (VKCAclEntry) e.nextElement();
	
				// look out for only acltypes of anyother(3)
				if ( aclEntries.getAclType().getAclTypeId().intValue() == VKCAclType.ANYOTHER_TYPE ) {
					if ( aclEntries.getPermission().getToken() == permToken ) {
						return true;
					}
				}
			}
/*
 		}
*/

		return false;
	}	

	private static final boolean hasUnauthenticatedPermission( VKCAcl acl, VKCUser user, char permToken ) {
/*		VKCAclEntry aclEntries = new VKCAclEntry();

		if (user.isGuestUser()) {
			
			// iterate through all entries
			for ( Enumeration e = acl.getAclEntries().elements(); e.hasMoreElements(); ) {
				aclEntries = (VKCAclEntry) e.nextElement();
	
				// look out for only acltypes of unauthenticated(8)
				if ( aclEntries.getAclType().getAclTypeId().intValue() == VKCAclType.UNAUTHENTICATED_TYPE ) {
					if ( aclEntries.getPermission().getToken() == permToken ) {
						return true;
					}
				}
			}
		}
*/		
		return false;
	}	
	
	public static final void setCategoryOwnerPermissions( Integer aclTypeId, Integer accessorId, Integer creatorId, VKCAcl acl ) throws DBException, AclException {
		if ( aclTypeId != null && accessorId != null && accessorId.intValue() >= 0 && creatorId != null && creatorId.intValue() >= 0 && acl != null ) {
			List permissions = VKCPermissionSerializer.retrieveContextPermissions( "category" );
			VKCAclType aclType = new VKCAclType();

			switch ( aclTypeId.intValue() ) {
				case VKCAclType.USER_TYPE:
					aclType = (VKCAclType) VKCAclTypeSerializer.retrieveAclTypes().get( "user" );
					break;
				case VKCAclType.GROUP_TYPE:
					aclType = (VKCAclType) VKCAclTypeSerializer.retrieveAclTypes().get( "group" );
					break;
				case VKCAclType.ANYOTHER_TYPE:
					aclType = (VKCAclType) VKCAclTypeSerializer.retrieveAclTypes().get( "anyother" );
					break;
				case VKCAclType.MASK_TYPE:
					aclType = (VKCAclType) VKCAclTypeSerializer.retrieveAclTypes().get( "mask" );
					break;
				case VKCAclType.OWNER_TYPE:
					aclType = (VKCAclType) VKCAclTypeSerializer.retrieveAclTypes().get( "owner" );
					break;
			}

			for ( int i = 0; i < permissions.size(); i++ ){
			    VKCPermission permission = (VKCPermission) permissions.get( i );
			    
			    if ( permission.getEnabled() != null && permission.getEnabled().intValue() != 0 ) {
				    VKCAclEntry aclEntry = new VKCAclEntry();
					aclEntry.setAclId( acl.getAclId() );
					aclEntry.setAclType( aclType );
					aclEntry.setAccessorId( accessorId );
					aclEntry.setCreatorId( creatorId );
					aclEntry.setPermission( permission );
					
					if ( !acl.contains( aclEntry ) ) {
						acl.addAclEntry( aclEntry );
					}
			    }
			}
		}

	}

	public static final void setCategoriesOwnerPermissions( VKCCategory category, Integer aclTypeId, Integer accessorId, Integer creatorId ) throws DBException, AclException {
		if ( category != null && category.getCategoryId() != null ) {
			for ( int i = 0; i < category.getSubCategories().size(); i++ ) {
				VKCSubCategory subCategory = (VKCSubCategory)category.getSubCategories().get( i );
				VKCCategory cat = VKCCategorySerializer.retrieveCategory( subCategory.getSubCategoryId() );
				setCategoriesOwnerPermissions( cat, aclTypeId, accessorId, creatorId );
			}
			
			setCategoryOwnerPermissions( aclTypeId, accessorId, creatorId, category.getAcl() );
			VKCCategorySerializer.saveCategory( category );
		}
	}
	
	/**
	 * Fügt die Rechte der Source-Acl zu der Target-Acl hinzu
	 *
	 * @param source Rechte welche hinzugefügt werden sollen
	 * @param target Ziel Acl zu welchem die Rechte hinzugefügt werden
	 * @throws AclException
	 * @throws Exception
	 */
	public static final void mergeAcls( VKCAcl source, VKCAcl target ) throws AclException, Exception {
	    if ( source != null && target != null ) {
	        Integer sourceAclId = source.getAclId();
	        
	        source.setAclId( target.getAclId() );
	        
	        for ( int i = 0; i < source.getAclEntries().size(); i++ ) {
	            VKCAclEntry entry = (VKCAclEntry)source.getAclEntries().get( i );
	            
	            if ( !target.contains( entry ) ) {
	                target.addAclEntry( entry.cloneEntry() );
	            }
	        }
	        
	        source.setAclId( sourceAclId );
	    }
	}
}
