commit e24797e92bfb2edd89d7956b9cae8455540bcad8
Author: Andreas Kohlbecker <a.kohlbecker@bgbm.org>
Date:   Mon Sep 10 06:14:54 2012 +0000

    all test SUCCESSFUL - (concept of implicitPermission and voters discarded and disabled)

diff --git a/.gitattributes b/.gitattributes
index 60b0fc5faa..48a0081061 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -1316,6 +1316,7 @@ cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permissi
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/DescriptionPermissionEvaluator.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/CdmPermissionVoter.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/GrantAlwaysVoter.java -text
+cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/TaxonBaseVoter.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/TaxonNodeVoter.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/replace/ReferringObjectMetadata.java -text
 cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/replace/ReferringObjectMetadataFactory.java -text
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/CdmPermissionClass.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/CdmPermissionClass.java
index 1b64dd8d65..d14d420b11 100644
--- a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/CdmPermissionClass.java
+++ b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/CdmPermissionClass.java
@@ -24,6 +24,7 @@ public enum CdmPermissionClass {
      * @return the CdmPermissionClass or null
      */
     public static CdmPermissionClass getValueOf(Object s){
+
         String permissionClassString ;
         if (s instanceof String){
             permissionClassString = (String)s;
@@ -35,6 +36,7 @@ public enum CdmPermissionClass {
 
             return null;
         }
+
         try{
             return CdmPermissionClass.valueOf(permissionClassString);
         }catch(IllegalArgumentException e){
@@ -45,6 +47,7 @@ public enum CdmPermissionClass {
             }
 
         }
+
         return null;
     }
 }
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/CdmPermissionVoter.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/CdmPermissionVoter.java
index 833c9ae014..8b9f046ce2 100644
--- a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/CdmPermissionVoter.java
+++ b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/CdmPermissionVoter.java
@@ -9,11 +9,18 @@
 */
 package eu.etaxonomy.cdm.persistence.hibernate.permission.voter;
 
+import java.util.Collection;
+
+import org.apache.log4j.Logger;
 import org.springframework.security.access.AccessDecisionVoter;
 import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.GrantedAuthority;
 
 import eu.etaxonomy.cdm.model.common.CdmBase;
 import eu.etaxonomy.cdm.persistence.hibernate.permission.AuthorityPermission;
+import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmPermission;
+import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmPermissionClass;
 
 /**
  * The <code>CdmPermissionVoter</code> provides access control votes for {@link CdmBase} objects.
@@ -24,6 +31,7 @@ import eu.etaxonomy.cdm.persistence.hibernate.permission.AuthorityPermission;
  */
 public abstract class CdmPermissionVoter implements AccessDecisionVoter {
 
+    public static final Logger logger = Logger.getLogger(CdmPermissionVoter.class);
 
     /* (non-Javadoc)
      * @see org.springframework.security.access.AccessDecisionVoter#supports(org.springframework.security.access.ConfigAttribute)
@@ -39,8 +47,184 @@ public abstract class CdmPermissionVoter implements AccessDecisionVoter {
      */
     @Override
     public boolean supports(Class<?> clazz) {
-        // all CdmPermissionVoters must support CdmBase.class
+        /* NOTE!!!
+         * Do not change this, all CdmPermissionVoters must support CdmBase.class
+         */
         return clazz.isInstance(CdmBase.class);
     }
 
+
+    /**
+     * Sets the Cdm type, or super type this Voter is responsible for.
+     */
+    abstract public Class<? extends CdmBase> getResponsibilityClass();
+
+
+    protected boolean isResponsibleFor(Object securedObject) {
+        return getResponsibilityClass().isAssignableFrom(securedObject.getClass());
+    }
+
+    protected boolean isResponsibleFor(CdmPermissionClass permissionClass) {
+        return getResponsibility().equals(permissionClass);
+    }
+
+    /**
+     * Get the according CdmPermissionClass matching {@link #getResponsibilityClass()} the cdm class this voter is responsible for.
+     * @return
+     */
+    protected CdmPermissionClass getResponsibility() {
+        return CdmPermissionClass.getValueOf(getResponsibilityClass());
+    }
+
+    /* (non-Javadoc)
+     * @see org.springframework.security.access.AccessDecisionVoter#vote(org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection)
+     */
+    @Override
+    public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
+
+        if(!isResponsibleFor(object)){
+            logger.debug("class missmatch => ACCESS_ABSTAIN");
+            return ACCESS_ABSTAIN;
+        }
+
+        if (logger.isDebugEnabled()){
+            logger.debug("authentication: " + authentication.getName() + ", object : " + object.toString() + ", attribute[0]:" + ((AuthorityPermission)attributes.iterator().next()).getAttribute());
+        }
+
+        // loop over all attributes = permissions of which at least one must match
+        // usually there is only one element in the collection!
+        for(ConfigAttribute attribute : attributes){
+            if(!(attribute instanceof AuthorityPermission)){
+                throw new RuntimeException("attributes must contain only AuthorityPermission");
+            }
+            AuthorityPermission evalPermission = (AuthorityPermission)attribute;
+
+            for (GrantedAuthority authority: authentication.getAuthorities()){
+                AuthorityPermission authorityPermission= new AuthorityPermission(authority.getAuthority());
+
+                // check if the voter is responsible for the permission to be evaluated
+                if(!evalPermission.getClassName().equals(getResponsibility())){
+                    logger.debug("not responsible for " + evalPermission.getClassName() + " -> skipping");
+                    continue;
+                }
+
+//                CdmPermissionVoter impliedVoter = findImpliedVoter(authorityPermission.getClassName());
+//                if(impliedVoter != null){
+//                    Set<CdmBase> targetObjects = findTargetObjectsForVoter(impliedVoter, (CdmBase) object);
+//                }
+                ValidationResult validationResult = new ValidationResult();
+
+                boolean isALL = authorityPermission.getClassName().equals(CdmPermissionClass.ALL);
+                validationResult.isClassMatch = isALL || authorityPermission.getClassName().equals(evalPermission.getClassName());
+
+                boolean isADMIN = authorityPermission.getPermission().equals(CdmPermission.ADMIN);
+                validationResult.isPermissionMatch = isADMIN || authorityPermission.getPermission().equals(evalPermission.getPermission());
+
+                validationResult.hasTargetUuid = authorityPermission.getTargetUUID() != null;
+                validationResult.isUuidMatch = validationResult.hasTargetUuid && authorityPermission.getTargetUUID().equals(((CdmBase)object).getUuid());
+
+                if ( !validationResult.hasTargetUuid && validationResult.isClassMatch && validationResult.isPermissionMatch){
+                    logger.debug("no tragetUuid, class & permission match => ACCESS_GRANTED");
+                    return ACCESS_GRANTED;
+                }
+                if ( validationResult.isUuidMatch  && validationResult.isClassMatch && validationResult.isPermissionMatch){
+                    logger.debug("permission, class and uuid are matching => ACCESS_GRANTED");
+                    return ACCESS_GRANTED;
+                }
+
+                // ask subclasses for further voting decisions
+                Integer furtherVotingResult = furtherVotingDescisions(authorityPermission, object, attributes, validationResult);
+                if(furtherVotingResult != null){
+                    return furtherVotingResult;
+                }
+
+            } // END Authorities loop
+        } // END attributes loop
+
+        logger.debug("ACCESS_DENIED");
+        return ACCESS_DENIED;
+    }
+
+    /**
+     * Override this method to implement specific decisions.
+     *
+     * @param authorityPermission
+     * @param object
+     * @param attributes
+     * @param validationResult
+     * @return
+     */
+    protected Integer furtherVotingDescisions(AuthorityPermission authorityPermission, Object object, Collection<ConfigAttribute> attributes,
+            ValidationResult validationResult) {
+        return null;
+    }
+
+    protected class ValidationResult {
+        boolean isPermissionMatch = false;
+        boolean isUuidMatch = false;
+        boolean isClassMatch = false;
+        boolean hasTargetUuid = false;
+    }
+
+    /* ===================== implicitPermission a nd voters disabled ================ */
+    /*implicitPermission
+    protected Set<CdmPermissionVoter> implicitPermissionClasses = new HashSet<CdmPermissionVoter>();
+    */
+    /**
+     * see {@link #setImplicitPermissionClasses(Set)}
+     *
+     * @return
+    public Set<CdmPermissionVoter> getImplicitPermissionClasses() {
+        return implicitPermissionClasses;
+    }
+     */
+
+    /**
+     * If the {@link GrantedAuthority} of an authentication contains {@link CdmPermission}s with
+     * at least one of the CdmPermissionClass in {@link #implicitPermissionClasses}, this means that
+     * this CdmPermission is treated as if it was a permission for the class this voter is responsible
+     * for (see {@link #responsibilityClass}).
+     * <p>
+     * <h4>Schematic example<h4>
+     * <ol>
+     * <li>required CdmPermission: TAXON.UPDATE => requited CdmPermission class is TAXON</li>
+     * <li>implicitPermissionClasses: {TAXONNODE}</li>
+     * <li>user has CdmPermission: TAXONNODE.UPDATE</li>
+     * <li>=> thus user also has implied permission TAXON.UPDATE</li>
+     * </ol>
+     *
+     *
+     * @param implicitPermissionClasses
+    public void setImplicitPermissionClasses(Set<CdmPermissionVoter> implicitPermissionClasses) {
+        this.implicitPermissionClasses = implicitPermissionClasses;
+    }
+     */
+
+    /*
+    protected CdmPermissionVoter findImpliedVoter(CdmPermissionClass assigned) {
+        for(CdmPermissionVoter impliedVoter : implicitPermissionClasses){
+            if(impliedVoter.isResponsibleFor(assigned)){
+                if(logger.isDebugEnabled()){
+                    logger.debug("* implicitClassMatch of " + assigned);
+                }
+                return impliedVoter;
+            }
+        }
+        return null;
+    }
+
+    protected Set<CdmBase> findTargetObjectsForVoter(CdmPermissionVoter impliedVoter, CdmBase object){
+
+        Set<CdmBase> targetObjects = new HashSet<CdmBase>();
+        if(impliedVoter.isResponsibleFor(Taxon.class)){
+            targetObjects.addAll(Taxon.class.cast(object).getTaxonNodes());
+        } else {
+            throw new RuntimeException("Not implemented for " + impliedVoter.getClass());
+        }
+        return targetObjects;
+    }
+    */
+
+
+
 }
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/GrantAlwaysVoter.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/GrantAlwaysVoter.java
index e47fca2ca1..dd45332099 100644
--- a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/GrantAlwaysVoter.java
+++ b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/GrantAlwaysVoter.java
@@ -12,10 +12,14 @@ package eu.etaxonomy.cdm.persistence.hibernate.permission.voter;
 import java.util.Collection;
 
 import org.springframework.security.access.ConfigAttribute;
+import org.springframework.security.access.vote.UnanimousBased;
 import org.springframework.security.core.Authentication;
 
+import eu.etaxonomy.cdm.model.common.CdmBase;
+
 /**
- * This voter always returns {@link n #ACCESS_GRANTED}
+ * This voter always returns {@link #ACCESS_GRANTED}.
+ * It is needed as default voter when using the {@link UnanimousBased}
  * @author andreas kohlbecker
  * @date Sep 4, 2012
  *
@@ -30,4 +34,9 @@ public class GrantAlwaysVoter extends CdmPermissionVoter {
         return ACCESS_GRANTED;
     }
 
+    @Override
+    public Class<? extends CdmBase> getResponsibilityClass() {
+        return CdmBase.class;
+    }
+
 }
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/TaxonBaseVoter.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/TaxonBaseVoter.java
new file mode 100644
index 0000000000..645195d587
--- /dev/null
+++ b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/TaxonBaseVoter.java
@@ -0,0 +1,35 @@
+// $Id$
+/**
+* Copyright (C) 2012 EDIT
+* European Distributed Institute of Taxonomy
+* http://www.e-taxonomy.eu
+*
+* The contents of this file are subject to the Mozilla Public License Version 1.1
+* See LICENSE.TXT at the top of this package for the full license terms.
+*/
+package eu.etaxonomy.cdm.persistence.hibernate.permission.voter;
+
+import org.apache.log4j.Logger;
+
+import eu.etaxonomy.cdm.model.common.CdmBase;
+import eu.etaxonomy.cdm.model.taxon.TaxonBase;
+
+/**
+ * @author andreas kohlbecker
+ * @date Sep 4, 2012
+ *
+ */
+public class TaxonBaseVoter extends CdmPermissionVoter {
+
+    public static final Logger logger = Logger.getLogger(TaxonBaseVoter.class);
+
+    @Override
+    public Class<? extends CdmBase> getResponsibilityClass() {
+        return TaxonBase.class;
+    }
+
+//    public TaxonBaseVoter(){
+////        implicitPermissionClasses.add(new TaxonNodeVoter()); //FIXME springify !!
+//    }
+
+}
diff --git a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/TaxonNodeVoter.java b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/TaxonNodeVoter.java
index b68dd71a47..a31c13fae8 100644
--- a/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/TaxonNodeVoter.java
+++ b/cdmlib-persistence/src/main/java/eu/etaxonomy/cdm/persistence/hibernate/permission/voter/TaxonNodeVoter.java
@@ -14,14 +14,10 @@ import java.util.UUID;
 
 import org.apache.log4j.Logger;
 import org.springframework.security.access.ConfigAttribute;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
 
 import eu.etaxonomy.cdm.model.common.CdmBase;
 import eu.etaxonomy.cdm.model.taxon.TaxonNode;
 import eu.etaxonomy.cdm.persistence.hibernate.permission.AuthorityPermission;
-import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmPermission;
-import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmPermissionClass;
 
 /**
  * @author andreas kohlbecker
@@ -32,12 +28,17 @@ public class TaxonNodeVoter extends CdmPermissionVoter {
 
     public static final Logger logger = Logger.getLogger(TaxonNodeVoter.class);
 
+    @Override
+    public Class<? extends CdmBase> getResponsibilityClass() {
+        return TaxonNode.class;
+    }
+
     /* (non-Javadoc)
      * @see org.springframework.security.access.AccessDecisionVoter#vote(org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection)
      */
+    /*
     @Override
     public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
-
         if(!(object instanceof TaxonNode)){
             logger.debug("class missmatch => ACCESS_ABSTAIN");
             return ACCESS_ABSTAIN;
@@ -64,7 +65,7 @@ public class TaxonNodeVoter extends CdmPermissionVoter {
                 boolean isPermissionMatch = isADMIN || authorityPermission.getPermission().equals(evalPermission.getPermission());
 
                 boolean hasTargetUuid = authorityPermission.getTargetUUID() != null;
-                boolean isUuidMatchInParentNodes = hasTargetUuid && findTargetUuidInParentNodes(authorityPermission.getTargetUUID(), node);
+
                 boolean isUuidMatch = hasTargetUuid && authorityPermission.getTargetUUID().equals(((CdmBase)object).getUuid());
 
                 if ( !hasTargetUuid && isClassMatch && isPermissionMatch){
@@ -85,6 +86,22 @@ public class TaxonNodeVoter extends CdmPermissionVoter {
         logger.debug("ACCESS_DENIED");
         return ACCESS_DENIED; // or Abstain???
     }
+    */
+
+    /* (non-Javadoc)
+     * @see eu.etaxonomy.cdm.persistence.hibernate.permission.voter.CdmPermissionVoter#furtherVotingDescisions(org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection, eu.etaxonomy.cdm.persistence.hibernate.permission.voter.TaxonBaseVoter.ValidationResult)
+     */
+    @Override
+    protected Integer furtherVotingDescisions(AuthorityPermission authorityPermission, Object object, Collection<ConfigAttribute> attributes,
+            ValidationResult validationResult) {
+
+        boolean isUuidMatchInParentNodes = validationResult.hasTargetUuid && findTargetUuidInParentNodes(authorityPermission.getTargetUUID(), (TaxonNode)object);
+        if ( isUuidMatchInParentNodes  && validationResult.isClassMatch && validationResult.isPermissionMatch){
+            logger.debug("permission, class and uuid in parent nodes are matching => ACCESS_GRANTED");
+            return ACCESS_GRANTED;
+        }
+        return null;
+    }
 
     /**
      * @param targetUuid
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/test/integration/CdmIntegrationTest.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/test/integration/CdmIntegrationTest.java
index ea355aacb7..9c4d9e2c81 100644
--- a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/test/integration/CdmIntegrationTest.java
+++ b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/test/integration/CdmIntegrationTest.java
@@ -285,7 +285,6 @@ public abstract class CdmIntegrationTest extends UnitilsJUnit4 {
             IDataSet actualDataSet = connection.createDataSet(tableNames);
             FlatXmlDataSet.write(actualDataSet, out);
 
-
         } catch (Exception e) {
             logger.error(e);
         } finally {
diff --git a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/test/integration/CdmTransactionalIntegrationTest.java b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/test/integration/CdmTransactionalIntegrationTest.java
index 10519ab25c..a86cc461d7 100644
--- a/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/test/integration/CdmTransactionalIntegrationTest.java
+++ b/cdmlib-persistence/src/test/java/eu/etaxonomy/cdm/test/integration/CdmTransactionalIntegrationTest.java
@@ -281,6 +281,7 @@ public abstract class CdmTransactionalIntegrationTest extends CdmIntegrationTest
 
         if (this.transactionStatus != null) {
             try {
+                logger.debug("Trying to commit or rollback");
                 if (commit) {
                     this.transactionManager.commit(this.transactionStatus);
                     logger.debug("Committed transaction after execution of test");
@@ -291,6 +292,7 @@ public abstract class CdmTransactionalIntegrationTest extends CdmIntegrationTest
                 }
             }
             finally {
+                logger.debug("Clearing transactionStatus");
                 this.transactionStatus = null;
             }
         }
@@ -300,10 +302,12 @@ public abstract class CdmTransactionalIntegrationTest extends CdmIntegrationTest
 
         if (this.transactionStatus != null) {
             try {
+                logger.debug("trying to rollback");
                 this.transactionManager.rollback(this.transactionStatus);
                 logger.debug("Rolled back transaction after execution of test.");
             }
             finally {
+                logger.debug("Clearing transactionStatus");
                 this.transactionStatus = null;
             }
         }
@@ -347,12 +351,19 @@ public abstract class CdmTransactionalIntegrationTest extends CdmIntegrationTest
      * </pre>
      */
     protected void commitAndStartNewTransaction(final String[] tableNames) {
-        setComplete();
-        endTransaction();
+        commit();
         if(logger.isDebugEnabled()){
             printDataSet(System.out, tableNames);
         }
         startNewTransaction();
     }
 
+    /**
+     *
+     */
+    protected void commit() {
+        setComplete();
+        endTransaction();
+    }
+
 }
diff --git a/cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/SecurityTest.java b/cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/SecurityTest.java
index 706e0a552e..f62f855d93 100644
--- a/cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/SecurityTest.java
+++ b/cdmlib-services/src/test/java/eu/etaxonomy/cdm/api/service/SecurityTest.java
@@ -56,6 +56,8 @@ import eu.etaxonomy.cdm.test.integration.CdmTransactionalIntegrationTestWithSecu
 @DataSet
 public class SecurityTest extends CdmTransactionalIntegrationTestWithSecurity{
 
+    private static final UUID UUID_ACHERONTIA_STYX = UUID.fromString("7b8b5cb3-37ba-4dba-91ac-4c6ffd6ac331");
+
     private static final UUID PART_EDITOR_UUID = UUID.fromString("38a251bd-0ba4-426f-8fcb-5c09560749a7");
 
     private static final String PASSWORD_TAXON_EDITOR = "test2";
@@ -213,6 +215,11 @@ public class SecurityTest extends CdmTransactionalIntegrationTestWithSecurity{
         } catch (RuntimeException e){
             exception = findThrowableOfTypeIn(EvaluationFailedException.class, e);
             logger.debug("Unexpected failure of evaluation.", exception);
+        } finally {
+            // needed in case saveOrUpdate was interrupted by the RuntimeException
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException
+            endTransaction();
+            startNewTransaction();
         }
         Assert.assertNull("must not fail here!", exception);
 
@@ -241,6 +248,11 @@ public class SecurityTest extends CdmTransactionalIntegrationTestWithSecurity{
         } catch (RuntimeException e){
             exception = findThrowableOfTypeIn(EvaluationFailedException.class, e);
             logger.debug("Expected failure of evaluation.", exception);
+        } finally {
+            // needed in case saveOrUpdate was interrupted by the RuntimeException
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException
+            endTransaction();
+            startNewTransaction();
         }
         Assert.assertNotNull("must fail here!", exception);
     }
@@ -272,24 +284,59 @@ public class SecurityTest extends CdmTransactionalIntegrationTestWithSecurity{
         // 1) test with admin account - should succeed
         authentication = authenticationManager.authenticate(tokenForAdmin);
         context.setAuthentication(authentication);
-        Taxon expectedTaxon = Taxon.NewInstance(null, null);
-        UUID uuid = taxonService.save(expectedTaxon);
-        commitAndStartNewTransaction(null);
-        TaxonBase<?> actualTaxon = taxonService.load(uuid);
-        assertEquals(expectedTaxon, actualTaxon);
 
-        actualTaxon.setName(BotanicalName.NewInstance(Rank.SPECIES()));
-        taxonService.saveOrUpdate(actualTaxon);
-        commitAndStartNewTransaction(null);
+        TaxonBase<?> taxon = taxonService.load(UUID_ACHERONTIA_STYX);
+        taxon.setDoubtful(true);
+        RuntimeException securityException= null;
+        try{
+            taxonService.saveOrUpdate(taxon);
+            commitAndStartNewTransaction(null);
+        } catch (RuntimeException e){
+            securityException  = findSecurityRuntimeException(e);
+            logger.debug("Unexpected failure of evaluation.", securityException);
+        } finally {
+            // needed in case saveOrUpdate was interrupted by the RuntimeException
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException
+            endTransaction();
+            startNewTransaction();
+        }
+        Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);
+        // reload taxon
+        taxon = taxonService.load(UUID_ACHERONTIA_STYX);
+        Assert.assertTrue("The change must be persited", taxon.isDoubtful());
 
         // 2) test with taxonEditor account - should succeed
         authentication = authenticationManager.authenticate(tokenForTaxonEditor);
         context.setAuthentication(authentication);
-        actualTaxon = taxonService.load(uuid);
 
-        actualTaxon.setDoubtful(true);
-        taxonService.saveOrUpdate(actualTaxon);
-        commitAndStartNewTransaction(null);
+        taxon = taxonService.load(UUID_ACHERONTIA_STYX);
+        taxon.setDoubtful(false);
+        securityException= null;
+        try{
+            taxonService.saveOrUpdate(taxon);
+            commitAndStartNewTransaction(null);
+        } catch (RuntimeException e){
+            securityException  = findSecurityRuntimeException(e);
+            logger.debug("Unexpected failure of evaluation.", securityException);
+        } finally {
+            // needed in case saveOrUpdate was interrupted by the RuntimeException
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException
+            endTransaction();
+            startNewTransaction();
+        }
+        Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);
+        // reload taxon
+        taxon = taxonService.load(UUID_ACHERONTIA_STYX);
+        Assert.assertFalse("The change must be persited", taxon.isDoubtful());
+
+        // 3) test with tokenForDescriptionEditor account - should fail
+//        authentication = authenticationManager.authenticate(tokenForTaxonEditor);
+//        context.setAuthentication(authentication);
+//        taxon = taxonService.load(uuid);
+//
+//        taxon.setDoubtful(true);
+//        taxonService.saveOrUpdate(taxon);
+//        commitAndStartNewTransaction(null);
 
     }
 
@@ -352,17 +399,58 @@ public class SecurityTest extends CdmTransactionalIntegrationTestWithSecurity{
 
     @Test
     public void testSaveSynonym(){
-        authentication = authenticationManager.authenticate(tokenForTaxonomist);
+
         SecurityContext context = SecurityContextHolder.getContext();
+
+        // 1) test for success
+        authentication = authenticationManager.authenticate(tokenForTaxonomist);
         context.setAuthentication(authentication);
 
+        RuntimeException securityException = null;
         Synonym syn = Synonym.NewInstance(BotanicalName.NewInstance(Rank.SPECIES()), null);
-        taxonService.saveOrUpdate(syn);
-        commitAndStartNewTransaction(null);
+        UUID synUuid = UUID.randomUUID();
+        syn.setUuid(synUuid);
+        try{
+            taxonService.saveOrUpdate(syn);
+            logger.debug("will commit ...");
+            commitAndStartNewTransaction(null);
+        } catch (RuntimeException e){
+            securityException = findSecurityRuntimeException(e);
+            logger.debug("Unexpected failure of evaluation.", securityException);
+        } finally {
+            // needed in case saveOrUpdate was interrupted by the RuntimeException
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException
+            endTransaction();
+            startNewTransaction();
+        }
+        Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);
+        Assert.assertNotNull("The new Synonym must be persited", taxonService.find(synUuid));
+
+        // 2) test for denial
+        authentication = authenticationManager.authenticate(tokenForDescriptionEditor);
+        context.setAuthentication(authentication);
+        securityException = null;
+        syn = Synonym.NewInstance(BotanicalName.NewInstance(Rank.SPECIES()), null);
+        synUuid = syn.getUuid();
+        try{
+            taxonService.saveOrUpdate(syn);
+            logger.debug("will commit ...");
+            commitAndStartNewTransaction(null);
+        } catch (RuntimeException e){
+            securityException = findSecurityRuntimeException(e);
+            logger.debug("Expected failure of evaluation: " + securityException.getClass());
+        } finally {
+            // needed in case saveOrUpdate was interrupted by the RuntimeException
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException
+            endTransaction();
+            startNewTransaction();
+        }
+
+        Assert.assertNotNull("evaluation must fail since the user is not permitted", securityException);
+        Assert.assertNull("The Synonym must not be persited", taxonService.find(synUuid));
     }
 
     @Test
-    @Ignore //FIXME test must not fail !!!!!
     public void testEditPartOfClassification(){
         /*
          * the user 'partEditor' has the following authorities:
@@ -379,17 +467,24 @@ public class SecurityTest extends CdmTransactionalIntegrationTestWithSecurity{
         context.setAuthentication(authentication);
 
         // test for success
+        RuntimeException securityException = null;
         TaxonNode acherontia_node = taxonNodeService.load(ACHERONTIA_NODE_UUID);
         long numOfChildNodes = acherontia_node.getChildNodes().size();
         TaxonNode childNode = acherontia_node.addChildTaxon(Taxon.NewInstance(BotanicalName.NewInstance(Rank.SPECIES()), null), null, null, null);
-        RuntimeException securityException = null;
+
         try{
             taxonNodeService.saveOrUpdate(acherontia_node);
             commitAndStartNewTransaction(null);
         } catch (RuntimeException e){
             securityException = findSecurityRuntimeException(e);
             logger.debug("Unexpected failure of evaluation.", securityException);
+        } finally {
+            // needed in case saveOrUpdate was interrupted by the RuntimeException
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException
+            endTransaction();
+            startNewTransaction();
         }
+
         acherontia_node = taxonNodeService.load(ACHERONTIA_NODE_UUID);
         Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);
         Assert.assertEquals("the acherontia_node must now have one more child node ", numOfChildNodes + 1 , acherontia_node.getChildNodes().size());
@@ -399,6 +494,7 @@ public class SecurityTest extends CdmTransactionalIntegrationTestWithSecurity{
         TaxonNode acherontiini_node = taxonNodeService.load(ACHERONTIINI_NODE_UUID);
         numOfChildNodes = acherontiini_node.getCountChildren();
         acherontiini_node.addChildTaxon(Taxon.NewInstance(BotanicalName.NewInstance(Rank.GENUS()), null), null, null, null);
+
         try{
             logger.debug("==============================");
             taxonNodeService.saveOrUpdate(acherontiini_node);
@@ -406,7 +502,13 @@ public class SecurityTest extends CdmTransactionalIntegrationTestWithSecurity{
         } catch (RuntimeException e){
             securityException = findSecurityRuntimeException(e);
             logger.debug("Expected failure of evaluation.", securityException);
+        } finally {
+            // needed in case saveOrUpdate was interrupted by the RuntimeException
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException
+            endTransaction();
+            startNewTransaction();
         }
+
         acherontiini_node = taxonNodeService.load(ACHERONTIINI_NODE_UUID);
         Assert.assertNotNull("evaluation must fail since the user is not permitted", securityException);
         Assert.assertEquals("the number of child nodes must be unchanged ", numOfChildNodes , acherontiini_node.getChildNodes().size());
diff --git a/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/SecurityTest.xml b/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/SecurityTest.xml
index a7cfc201af..9d74a9a8ef 100644
--- a/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/SecurityTest.xml
+++ b/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/api/service/SecurityTest.xml
@@ -11,7 +11,7 @@
   <PERMISSIONGROUP_GRANTEDAUTHORITYIMPL PERMISSIONGROUP_ID="2" GRANTEDAUTHORITIES_ID="4"/>
   <PERMISSIONGROUP_GRANTEDAUTHORITYIMPL PERMISSIONGROUP_ID="3" GRANTEDAUTHORITIES_ID="8"/>
   <PERMISSIONGROUP_GRANTEDAUTHORITYIMPL PERMISSIONGROUP_ID="3" GRANTEDAUTHORITIES_ID="9"/>
-  <!-- <PERMISSIONGROUP_GRANTEDAUTHORITYIMPL PERMISSIONGROUP_ID="3" GRANTEDAUTHORITIES_ID="10"/> -->
+  <PERMISSIONGROUP_GRANTEDAUTHORITYIMPL PERMISSIONGROUP_ID="3" GRANTEDAUTHORITIES_ID="10"/>
 
 
   <GRANTEDAUTHORITYIMPL ID="1"  UUID="441a3c40-0c84-11de-8c30-0800200c9a66" CREATED="2009-02-03 17:52:26.0" AUTHORITY="TAXONBASE.READ"/>
diff --git a/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/applicationContext-securityTest.xml b/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/applicationContext-securityTest.xml
index 9595299acf..726a4659d4 100644
--- a/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/applicationContext-securityTest.xml
+++ b/cdmlib-services/src/test/resources/eu/etaxonomy/cdm/applicationContext-securityTest.xml
@@ -31,6 +31,7 @@
             <list>
                 <bean class="eu.etaxonomy.cdm.persistence.hibernate.permission.voter.GrantAlwaysVoter" />
                 <bean class="eu.etaxonomy.cdm.persistence.hibernate.permission.voter.TaxonNodeVoter" />
+                <bean class="eu.etaxonomy.cdm.persistence.hibernate.permission.voter.TaxonBaseVoter" />
             </list>
         </property>
     </bean>
diff --git a/cdmlib-services/src/test/resources/log4j.properties b/cdmlib-services/src/test/resources/log4j.properties
index 0790c7dfbe..b5dc4435b8 100644
--- a/cdmlib-services/src/test/resources/log4j.properties
+++ b/cdmlib-services/src/test/resources/log4j.properties
@@ -123,6 +123,7 @@ log4j.logger.eu.etaxonomy.cdm.persistence.hibernate.permission=debug
 log4j.logger.eu.etaxonomy.cdm.persistence.hibernate.CdmSecurityHibernateInterceptor=debug
 log4j.logger.org.springframework.security.access.intercept=debug
 log4j.logger.org.springframework.security.access.vote=debug
+log4j.logger.eu.etaxonomy.cdm.test.integration.CdmTransactionalIntegrationTest=debug
 
 #### Lucene Fulltext index and cdmlib search facility####
 #log4j.logger.org.apache.lucene=debug
