merge trunk and bugfix linkbackuri jaxb
[cdmlib.git] / cdmlib-services / src / test / java / eu / etaxonomy / cdm / api / service / SecurityTest.java
index 6b50e30341ed371303edc044573df5695390d9aa..9cf33220405652b25a61bfb65ac784dd782f3026 100644 (file)
@@ -1,13 +1,18 @@
+/**\r
+ * Copyright (C) 2011 EDIT\r
+ * European Distributed Institute of Taxonomy\r
+ * http://www.e-taxonomy.eu\r
+ *\r
+ * The contents of this file are subject to the Mozilla Public License Version 1.1\r
+ * See LICENSE.TXT at the top of this package for the full license terms.\r
+ */\r
 package eu.etaxonomy.cdm.api.service;\r
 \r
 import static org.junit.Assert.assertEquals;\r
 import static org.junit.Assert.assertFalse;\r
 import static org.junit.Assert.assertTrue;\r
 \r
-\r
-import java.util.ArrayList;\r
 import java.util.Collection;\r
-import java.util.Iterator;\r
 import java.util.List;\r
 import java.util.Set;\r
 import java.util.UUID;\r
@@ -15,78 +20,71 @@ import java.util.UUID;
 import javax.sql.DataSource;\r
 \r
 import org.apache.log4j.Logger;\r
-\r
 import org.junit.Assert;\r
 import org.junit.Before;\r
 import org.junit.Ignore;\r
 import org.junit.Test;\r
-import org.junit.runner.RunWith;\r
-\r
-import org.springframework.beans.factory.annotation.Autowired;\r
-import org.springframework.security.access.vote.RoleVoter;\r
+import org.springframework.security.access.AccessDeniedException;\r
 import org.springframework.security.authentication.AuthenticationManager;\r
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\r
-import org.springframework.security.authentication.dao.ReflectionSaltSource;\r
-import org.springframework.security.authentication.encoding.Md5PasswordEncoder;\r
+import org.springframework.security.authentication.dao.SaltSource;\r
+import org.springframework.security.authentication.encoding.PasswordEncoder;\r
 import org.springframework.security.core.Authentication;\r
 import org.springframework.security.core.GrantedAuthority;\r
 import org.springframework.security.core.context.SecurityContext;\r
 import org.springframework.security.core.context.SecurityContextHolder;\r
-import org.springframework.test.annotation.ExpectedException;\r
 import org.springframework.transaction.PlatformTransactionManager;\r
-\r
-\r
-import org.unitils.database.annotations.Transactional;\r
-import org.unitils.UnitilsJUnit4TestClassRunner;\r
 import org.unitils.database.annotations.TestDataSource;\r
-import org.unitils.database.util.TransactionMode;\r
 import org.unitils.dbunit.annotation.DataSet;\r
-import org.unitils.spring.annotation.SpringApplicationContext;\r
-import org.unitils.spring.annotation.SpringBeanByName;\r
+import org.unitils.spring.annotation.SpringBean;\r
 import org.unitils.spring.annotation.SpringBeanByType;\r
 \r
-\r
-import eu.etaxonomy.cdm.api.service.config.ITaxonServiceConfigurator;\r
-import eu.etaxonomy.cdm.api.service.config.TaxonServiceConfiguratorImpl;\r
-import eu.etaxonomy.cdm.api.service.pager.Pager;\r
 import eu.etaxonomy.cdm.database.EvaluationFailedException;\r
-import eu.etaxonomy.cdm.model.common.Language;\r
 import eu.etaxonomy.cdm.model.common.User;\r
-\r
-\r
 import eu.etaxonomy.cdm.model.description.DescriptionElementBase;\r
-import eu.etaxonomy.cdm.model.description.Distribution;\r
 import eu.etaxonomy.cdm.model.description.Feature;\r
 import eu.etaxonomy.cdm.model.description.TaxonDescription;\r
 import eu.etaxonomy.cdm.model.description.TextData;\r
-\r
-import eu.etaxonomy.cdm.model.media.Media;\r
 import eu.etaxonomy.cdm.model.name.BotanicalName;\r
 import eu.etaxonomy.cdm.model.name.Rank;\r
 import eu.etaxonomy.cdm.model.taxon.Synonym;\r
-\r
-import eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType;\r
 import eu.etaxonomy.cdm.model.taxon.Taxon;\r
 import eu.etaxonomy.cdm.model.taxon.TaxonBase;\r
 import eu.etaxonomy.cdm.model.taxon.TaxonNode;\r
-import eu.etaxonomy.cdm.persistence.dao.BeanInitializer;\r
 import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmPermissionEvaluator;\r
-import eu.etaxonomy.cdm.test.integration.CdmTransactionalIntegrationTest;\r
+import eu.etaxonomy.cdm.persistence.hibernate.permission.Operation;\r
+import eu.etaxonomy.cdm.persistence.query.MatchMode;\r
 import eu.etaxonomy.cdm.test.integration.CdmTransactionalIntegrationTestWithSecurity;\r
 \r
 \r
-\r
-\r
-@RunWith(UnitilsJUnit4TestClassRunner.class)\r
 @DataSet\r
 public class SecurityTest extends CdmTransactionalIntegrationTestWithSecurity{\r
-private static final Logger logger = Logger.getLogger(TaxonServiceImplTest.class);\r
 \r
-/**\r
- * The transaction manager to use\r
- */\r
-@SpringBeanByType\r
-PlatformTransactionManager transactionManager;\r
+    private static final UUID UUID_ACHERONTINII = UUID.fromString("928a0167-98cd-4555-bf72-52116d067625");\r
+\r
+    private static final UUID UUID_ACHERONTIA_STYX = UUID.fromString("7b8b5cb3-37ba-4dba-91ac-4c6ffd6ac331");\r
+\r
+    private static final UUID UUID_LACTUCA = UUID.fromString("b2b007a4-9c8c-43a1-8da4-20ed85464cf2");\r
+\r
+    private static final UUID PART_EDITOR_UUID = UUID.fromString("38a251bd-0ba4-426f-8fcb-5c09560749a7");\r
+\r
+    private static final String PASSWORD_TAXON_EDITOR = "test2";\r
+\r
+    private static final String PASSWORD_ADMIN = "sPePhAz6";\r
+\r
+    private static final UUID ACHERONTIA_NODE_UUID = UUID.fromString("20c8f083-5870-4cbd-bf56-c5b2b98ab6a7");\r
+\r
+    private static final UUID ACHERONTIINI_NODE_UUID = UUID.fromString("cecfa77f-f26a-4476-9d87-a8d993cb55d9");\r
+\r
+    private static final UUID ACHERONTIA_LACHESIS_UUID = UUID.fromString("bc09aca6-06fd-4905-b1e7-cbf7cc65d783");\r
+\r
+    private static final Logger logger = Logger.getLogger(SecurityTest.class);\r
+\r
+    /**\r
+     * The transaction manager to use\r
+     */\r
+    @SpringBeanByType\r
+    PlatformTransactionManager transactionManager;\r
 \r
     @SpringBeanByType\r
     private ITaxonService taxonService;\r
@@ -109,14 +107,187 @@ PlatformTransactionManager transactionManager;
     @SpringBeanByType\r
     private AuthenticationManager authenticationManager;\r
 \r
+    @SpringBeanByType\r
+    private SaltSource saltSource;\r
+\r
+    @SpringBeanByType\r
+    private PasswordEncoder passwordEncoder;\r
+\r
+    @SpringBean("cdmPermissionEvaluator")\r
+    private CdmPermissionEvaluator permissionEvaluator;\r
 \r
+    private UsernamePasswordAuthenticationToken tokenForAdmin;\r
 \r
-    private UsernamePasswordAuthenticationToken token;\r
+    private UsernamePasswordAuthenticationToken tokenForTaxonEditor;\r
+\r
+    private UsernamePasswordAuthenticationToken tokenForDescriptionEditor;\r
+\r
+    private UsernamePasswordAuthenticationToken tokenForPartEditor;\r
+\r
+    private UsernamePasswordAuthenticationToken tokenForTaxonomist;\r
+\r
+    private UsernamePasswordAuthenticationToken tokenForUserManager;\r
 \r
 \r
     @Before\r
     public void setUp(){\r
-        token = new UsernamePasswordAuthenticationToken("ben", "sPePhAz6");\r
+        /* User 'admin':\r
+            - ROLE_ADMIN\r
+            - TAXONBASE.READ\r
+            - TAXONBASE.CREATE\r
+            - TAXONBASE.DELETE\r
+            - TAXONBASE.UPDATE\r
+        */\r
+        tokenForAdmin = new UsernamePasswordAuthenticationToken("admin", PASSWORD_ADMIN);\r
+\r
+        /* User 'userManager':\r
+            - ROLE_ADMIN\r
+            - TAXONBASE.READ\r
+            - TAXONBASE.CREATE\r
+            - TAXONBASE.DELETE\r
+            - TAXONBASE.UPDATE\r
+        */\r
+        tokenForUserManager = new UsernamePasswordAuthenticationToken("userManager", PASSWORD_ADMIN);\r
+\r
+        /* User 'taxonEditor':\r
+            - TAXONBASE.CREATE\r
+            - TAXONBASE.UPDATE\r
+        */\r
+        tokenForTaxonEditor = new UsernamePasswordAuthenticationToken("taxonEditor", PASSWORD_TAXON_EDITOR);\r
+\r
+        /*  User 'descriptionEditor':\r
+            - DESCRIPTIONBASE.CREATE\r
+            - DESCRIPTIONBASE.UPDATE\r
+            - DESCRIPTIONELEMENT(Ecology).CREATE\r
+            - DESCRIPTIONELEMENT(Ecology).UPDATE\r
+         */\r
+        tokenForDescriptionEditor = new UsernamePasswordAuthenticationToken("descriptionEditor", "test");\r
+\r
+        /* User 'partEditor':\r
+            - TAXONBASE.ADMIN\r
+            - TAXONNODE.CREATE{20c8f083-5870-4cbd-bf56-c5b2b98ab6a7}\r
+            - TAXONNODE.UPDATE{20c8f083-5870-4cbd-bf56-c5b2b98ab6a7}\r
+         */\r
+        tokenForPartEditor = new UsernamePasswordAuthenticationToken("partEditor", "test4");\r
+\r
+        /* User 'taxonomist':\r
+            - TAXONBASE.READ\r
+            - TAXONBASE.CREATE\r
+            - TAXONBASE.DELETE\r
+            - TAXONBASE.UPDATE\r
+         */\r
+        tokenForTaxonomist = new UsernamePasswordAuthenticationToken("taxonomist", "test4");\r
+    }\r
+\r
+\r
+    /**\r
+     * no assertions in this test, since it is only used to create password hashes for test data\r
+     */\r
+    @Test\r
+    public void testEncryptPassword(){\r
+\r
+        String password = PASSWORD_ADMIN;\r
+        User user = User.NewInstance("userManager", "");\r
+\r
+        Object salt = this.saltSource.getSalt(user);\r
+        String passwordEncrypted = passwordEncoder.encodePassword(password, salt);\r
+        logger.info("encrypted password: " + passwordEncrypted );\r
+    }\r
+\r
+    @Test\r
+    @DataSet\r
+    public void testHasPermission(){\r
+\r
+        Taxon taxon = Taxon.NewInstance(BotanicalName.NewInstance(Rank.GENUS()),null);\r
+\r
+        authentication = authenticationManager.authenticate(tokenForTaxonomist);\r
+        boolean hasPermission = permissionEvaluator.hasPermission(authentication, taxon, Operation.UPDATE);\r
+        assertTrue(hasPermission);\r
+\r
+        authentication = authenticationManager.authenticate(tokenForDescriptionEditor);\r
+        hasPermission = permissionEvaluator.hasPermission(authentication, taxon, Operation.UPDATE);\r
+        assertFalse(hasPermission);\r
+    }\r
+\r
+    @Test\r
+    @DataSet\r
+    public void testListByUsernameAllow(){\r
+\r
+        authentication = authenticationManager.authenticate(tokenForTaxonomist);\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        context.setAuthentication(authentication);\r
+\r
+        List<User> userList = userService.listByUsername("Editor", MatchMode.ANYWHERE, null, null, 0, null, null);\r
+        Assert.assertTrue("The user list must have elements", userList.size() > 0 );\r
+    }\r
+\r
+    @Test\r
+    @DataSet\r
+    public void testUserService_CreateDeny(){\r
+\r
+        authentication = authenticationManager.authenticate(tokenForTaxonomist);\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        context.setAuthentication(authentication);\r
+\r
+        RuntimeException exception = null;\r
+        try {\r
+            userService.createUser(User.NewInstance("new guy", "alkjdsfalkj"));\r
+            commitAndStartNewTransaction(null);\r
+        } catch (AccessDeniedException e){\r
+            logger.debug("Expected failure of evaluation.", e);\r
+            exception = e;\r
+        } catch (RuntimeException e){\r
+            exception = findThrowableOfTypeIn(EvaluationFailedException.class, e);\r
+            logger.debug("Expected failure of evaluation.", exception);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+        Assert.assertNotNull("Must fail here!", exception);\r
+\r
+    }\r
+\r
+    @Test\r
+    @DataSet\r
+    public void testUserService_CreateAllow(){\r
+\r
+        authentication = authenticationManager.authenticate(tokenForUserManager);\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        context.setAuthentication(authentication);\r
+\r
+        RuntimeException exception = null;\r
+        try {\r
+            userService.createUser(User.NewInstance("new guy", "alkjdsfalkj"));\r
+            commitAndStartNewTransaction(null);\r
+        } catch (AccessDeniedException e){\r
+            logger.error("Unexpected failure of evaluation.", e);\r
+            exception = e;\r
+        } catch (RuntimeException e){\r
+            exception = findThrowableOfTypeIn(EvaluationFailedException.class, e);\r
+            logger.error("unexpected failure of evaluation.", exception);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+        Assert.assertNull("Must not fail here!", exception);\r
+\r
+    }\r
+\r
+\r
+    @Test\r
+    @DataSet\r
+    @Ignore // FIXME http://dev.e-taxonomy.eu/trac/ticket/3098\r
+    public void testHasPermissions(){\r
+\r
+        Taxon taxon = Taxon.NewInstance(BotanicalName.NewInstance(Rank.GENUS()),null);\r
+\r
+        authentication = authenticationManager.authenticate(tokenForTaxonomist);\r
+        boolean hasPermission = permissionEvaluator.hasPermission(authentication, taxon, Operation.ALL);\r
+        assertTrue(hasPermission);\r
     }\r
 \r
 \r
@@ -125,37 +296,119 @@ PlatformTransactionManager transactionManager;
      */\r
     @Test\r
     public final void testSaveTaxon() {\r
-        /*\r
-        Md5PasswordEncoder encoder =new Md5PasswordEncoder();\r
-        ReflectionSaltSource saltSource = new ReflectionSaltSource();\r
-        saltSource.setUserPropertyToUse("getUsername");\r
-        User user = User.NewInstance("partEditor", "test4");\r
-        System.err.println(encoder.encodePassword("test4", saltSource.getSalt(user)));\r
 \r
-        */\r
-        authentication = authenticationManager.authenticate(token);\r
+        authentication = authenticationManager.authenticate(tokenForAdmin);\r
         SecurityContext context = SecurityContextHolder.getContext();\r
         context.setAuthentication(authentication);\r
 \r
         Taxon expectedTaxon = Taxon.NewInstance(BotanicalName.NewInstance(Rank.SPECIES()), null);\r
         UUID uuid = taxonService.save(expectedTaxon);\r
+        commitAndStartNewTransaction(null);\r
         //taxonService.getSession().flush();\r
         TaxonBase<?> actualTaxon = taxonService.load(uuid);\r
         assertEquals(expectedTaxon, actualTaxon);\r
 \r
-        token = new UsernamePasswordAuthenticationToken("taxonEditor", "test2");\r
-        authentication = authenticationManager.authenticate(token);\r
+        authentication = authenticationManager.authenticate(tokenForTaxonEditor);\r
         context = SecurityContextHolder.getContext();\r
         context.setAuthentication(authentication);\r
         expectedTaxon = Taxon.NewInstance(BotanicalName.NewInstance(Rank.GENUS()), null);\r
         taxonService.saveOrUpdate(actualTaxon);\r
+        commitAndStartNewTransaction(null);\r
+\r
+    }\r
+\r
+    @Test\r
+    public void testChangeOwnPassword(){\r
+\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        // authenticate as admin\r
+        authentication = authenticationManager.authenticate(tokenForTaxonEditor);\r
+        context.setAuthentication(authentication);\r
+\r
+//        User currentUser =  (User) context.getAuthentication().getPrincipal();\r
+\r
+        String newPass = "poiweorijo";\r
+        userService.changePassword(PASSWORD_TAXON_EDITOR, newPass);\r
+        commitAndStartNewTransaction(null);\r
+\r
+        // try to re-authenticate user with changed password\r
+        UsernamePasswordAuthenticationToken newTokenForTaxonEditor = new UsernamePasswordAuthenticationToken("taxonEditor", newPass);\r
+        authentication = authenticationManager.authenticate(newTokenForTaxonEditor);\r
+    }\r
+\r
+    @Test\r
+    public void testChangeOthersPasswordAllow(){\r
+\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        RuntimeException exception = null;\r
+\r
+        // (1) authenticate as admin\r
+        authentication = authenticationManager.authenticate(tokenForAdmin);\r
+        context.setAuthentication(authentication);\r
 \r
 \r
+        try{\r
+            userService.changePasswordForUser("taxonomist", "zuaisd");\r
+            commitAndStartNewTransaction(null);\r
+        } catch (AccessDeniedException e){\r
+            logger.error("Unexpected failure of evaluation.", e);\r
+            exception = e;\r
+        } catch (RuntimeException e){\r
+            exception = findThrowableOfTypeIn(EvaluationFailedException.class, e);\r
+            logger.error("Unexpected failure of evaluation.", exception);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+        Assert.assertNull("must not fail here!", exception);\r
+\r
+        // ok, now try authenticating taxonomist with new password\r
+        UsernamePasswordAuthenticationToken newToken = new UsernamePasswordAuthenticationToken("taxonomist", "zuaisd");\r
+        authentication = authenticationManager.authenticate(newToken);\r
     }\r
+\r
+    @Test\r
+    public void testChangeOthersPasswordDeny(){\r
+\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        RuntimeException exception = null;\r
+\r
+        // (2) authenticate as under privileged user - not an admin !!!\r
+        authentication = authenticationManager.authenticate(tokenForDescriptionEditor);\r
+        context.setAuthentication(authentication);\r
+\r
+        // check test preconditions user name and authorities\r
+        Assert.assertEquals("descriptionEditor", context.getAuthentication().getName());\r
+        Collection<? extends GrantedAuthority> authorities = context.getAuthentication().getAuthorities();\r
+        for(GrantedAuthority authority: authorities){\r
+            // role prefix 'ROLE_' is defined in org.springframework.security.access.vote.RoleVoter !!!\r
+            Assert.assertNotSame("user must not have authority 'ROLE_ADMIN'", "ROLE_ADMIN", authority.getAuthority());\r
+        }\r
+        // finally perform the test :\r
+        try{\r
+            userService.changePasswordForUser("partEditor", "poiweorijo");\r
+            commitAndStartNewTransaction(null);\r
+        } catch (AccessDeniedException e){\r
+            logger.debug("Expected failure of evaluation.", e);\r
+            exception = e;\r
+        } catch (RuntimeException e){\r
+            exception = findThrowableOfTypeIn(EvaluationFailedException.class, e);\r
+            logger.debug("Expected failure of evaluation.", exception);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+        Assert.assertNotNull("must fail here!", exception);\r
+    }\r
+\r
     @Test\r
     public void testUpdateUser(){\r
 \r
-        authentication = authenticationManager.authenticate(token);\r
+        authentication = authenticationManager.authenticate(tokenForAdmin);\r
         SecurityContext context = SecurityContextHolder.getContext();\r
         context.setAuthentication(authentication);\r
         String username = "standardUser";\r
@@ -168,138 +421,491 @@ PlatformTransactionManager transactionManager;
         userService.updateUser(user);\r
         userService.update(user);\r
         userService.saveOrUpdate(user);\r
+        commitAndStartNewTransaction(null);\r
+\r
     }\r
 \r
+    /**\r
+     * test with admin account - should succeed\r
+     */\r
     @Test\r
-    public final void testSaveOrUpdateTaxon() {\r
-        authentication = authenticationManager.authenticate(token);\r
+    public final void testTaxonSaveOrUpdateAllow_1() {\r
+\r
         SecurityContext context = SecurityContextHolder.getContext();\r
+\r
+        authentication = authenticationManager.authenticate(tokenForAdmin);\r
         context.setAuthentication(authentication);\r
-        Taxon expectedTaxon = Taxon.NewInstance(null, null);\r
-        UUID uuid = taxonService.save(expectedTaxon);\r
-        TaxonBase<?> actualTaxon = taxonService.load(uuid);\r
-        assertEquals(expectedTaxon, actualTaxon);\r
+        RuntimeException securityException= null;\r
+\r
+        TaxonBase<?> taxon = taxonService.load(UUID_ACHERONTIA_STYX);\r
+        Assert.assertFalse(taxon.isDoubtful());\r
+        taxon.setDoubtful(true);\r
+        try{\r
+            taxonService.saveOrUpdate(taxon);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException  = findSecurityRuntimeException(e);\r
+            logger.error("Unexpected failure of evaluation.", e);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+        Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);\r
+        // reload taxon\r
+        taxon = taxonService.load(UUID_ACHERONTIA_STYX);\r
+        Assert.assertTrue("The change must be persited", taxon.isDoubtful());\r
+    }\r
 \r
-        actualTaxon.setName(BotanicalName.NewInstance(Rank.SPECIES()));\r
-        taxonService.saveOrUpdate(actualTaxon);\r
+    /**\r
+     * test with taxonEditor account - should succeed\r
+     */\r
+    @Test\r
+    public final void testTaxonSaveOrUpdateAllow_2() {\r
+\r
+\r
+        RuntimeException securityException= null;\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+\r
+         // taxonEditor account - should succeed\r
+        authentication = authenticationManager.authenticate(tokenForTaxonEditor);\r
 \r
-        token = new UsernamePasswordAuthenticationToken("taxonEditor", "test2");\r
-        authentication = authenticationManager.authenticate(token);\r
-        context = SecurityContextHolder.getContext();\r
         context.setAuthentication(authentication);\r
-        actualTaxon = taxonService.load(uuid);\r
 \r
-        actualTaxon.setDoubtful(true);\r
-        taxonService.saveOrUpdate(actualTaxon);\r
+        TaxonBase<?>  taxon = taxonService.load(UUID_ACHERONTIA_STYX);\r
+        Assert.assertFalse(taxon.isDoubtful());\r
+        taxon.setDoubtful(true);\r
+        try{\r
+            taxonService.saveOrUpdate(taxon);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException  = findSecurityRuntimeException(e);\r
+            logger.error("Unexpected failure of evaluation.", e);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+        Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);\r
+        // reload taxon\r
+        taxon = taxonService.load(UUID_ACHERONTIA_STYX);\r
+        Assert.assertTrue("The change must be persited", taxon.isDoubtful());\r
+    }\r
+\r
+    /**\r
+     * test with tokenForDescriptionEditor account - should fail\r
+     */\r
+    @Test\r
+    public final void testTaxonSaveOrUpdateDeny_2() {\r
+\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        RuntimeException securityException = null;\r
+\r
+        authentication = authenticationManager.authenticate(tokenForDescriptionEditor);\r
+        context.setAuthentication(authentication);\r
 \r
+        TaxonBase<?> taxon = taxonService.load(UUID_ACHERONTIA_STYX);\r
+\r
+        Assert.assertFalse(taxon.isDoubtful());\r
+        taxon.setDoubtful(true);\r
+        try {\r
+            taxonService.saveOrUpdate(taxon);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException = findSecurityRuntimeException(e);\r
+            logger.debug("Expected failure of evaluation.", securityException);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+\r
+        Assert.assertNotNull("evaluation must fail since the user is not permitted", securityException);\r
+        // reload taxon\r
+        taxon = taxonService.load(UUID_ACHERONTIA_STYX);\r
+        Assert.assertFalse("The change must not be persited", taxon.isDoubtful());\r
     }\r
 \r
+    /**\r
+     * test with admin account - should succeed\r
+     */\r
+    @Test\r
+    public final void testTaxonDeleteAllow_1() {\r
 \r
+        SecurityContext context = SecurityContextHolder.getContext();\r
 \r
+        authentication = authenticationManager.authenticate(tokenForAdmin);\r
+        context.setAuthentication(authentication);\r
+        RuntimeException securityException= null;\r
+\r
+        TaxonBase<?> taxon = taxonService.load(UUID_LACTUCA);\r
+        try{\r
+            taxonService.delete(taxon);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException  = findSecurityRuntimeException(e);\r
+            logger.error("Unexpected failure of evaluation.", e);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+        Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);\r
+        // reload taxon\r
+        taxon = taxonService.load(UUID_LACTUCA);\r
+        Assert.assertNull("The taxon must be deleted", taxon);\r
+    }\r
+\r
+    /**\r
+     * test with admin account - should succeed\r
+     */\r
     @Test\r
-    public void testCascadingInSpringSecurityAccesDenied(){\r
-        /*authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("partEditor", "test4"));\r
+    @Ignore\r
+    /*FIXME fails due to org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations)\r
+     *       see ticket #3086\r
+     */\r
+    public final void testTaxonDeleteAllow_2() {\r
+\r
         SecurityContext context = SecurityContextHolder.getContext();\r
+\r
+        authentication = authenticationManager.authenticate(tokenForAdmin);\r
         context.setAuthentication(authentication);\r
-        */\r
+        RuntimeException securityException= null;\r
+\r
+        TaxonBase<?> taxon = taxonService.load(UUID_ACHERONTINII);\r
+        try{\r
+            taxonService.delete(taxon);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException  = findSecurityRuntimeException(e);\r
+            logger.error("Unexpected failure of evaluation.", e);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+        Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);\r
+        // reload taxon\r
+        taxon = taxonService.load(UUID_ACHERONTINII);\r
+        Assert.assertNull("The taxon must be deleted", taxon);\r
+    }\r
+\r
+\r
+    /**\r
+     * test with tokenForDescriptionEditor account - should fail\r
+     */\r
+    @Test\r
+    public final void testTaxonDeleteDeny() {\r
 \r
-        authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("taxonEditor", "test2"));\r
         SecurityContext context = SecurityContextHolder.getContext();\r
+        RuntimeException securityException = null;\r
+\r
+        authentication = authenticationManager.authenticate(tokenForDescriptionEditor);\r
         context.setAuthentication(authentication);\r
-        CdmPermissionEvaluator permissionEvaluator = new CdmPermissionEvaluator();\r
 \r
-        Taxon taxon =(Taxon) taxonService.load(UUID.fromString("bc09aca6-06fd-4905-b1e7-cbf7cc65d783"));\r
-        taxon.setDoubtful(false);\r
-        assertTrue(permissionEvaluator.hasPermission(authentication, taxon, "UPDATE"));\r
-        taxonService.save(taxon);\r
-        taxon = null;\r
-        commitAndStartNewTransaction(null);\r
+        TaxonBase<?> taxon = taxonService.load(UUID_LACTUCA);\r
+\r
+        try {\r
+            taxonService.delete(taxon);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException = findSecurityRuntimeException(e);\r
+            logger.debug("Expected failure of evaluation.", securityException);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+\r
+        Assert.assertNotNull("evaluation must fail since the user is not permitted", securityException);\r
+        // reload taxon\r
+        taxon = taxonService.load(UUID_LACTUCA);\r
+        Assert.assertNotNull("The change must still exist", taxon);\r
+    }\r
 \r
-        //during cascading the permissions are not evaluated, but with hibernate listener every database transaction can be interrupted, but how to manage it,\r
-        //when someone has the rights to save descriptions, but not taxa (the editor always saves everything by saving the taxon)\r
-        //taxonService.saveOrUpdate(taxon);\r
 \r
+    @Test\r
+    @Ignore //FIXME: adding taxa to a description must be protected at the side of the Description itself!!\r
+            //        => protecting method TaxonDescription.setTaxon() ?\r
+    public void testAddDescriptionToTaxon(){\r
 \r
-        authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("descriptionEditor", "test"));\r
-        context = SecurityContextHolder.getContext();\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        authentication = authenticationManager.authenticate(tokenForDescriptionEditor);\r
         context.setAuthentication(authentication);\r
 \r
-        //taxonService.saveOrUpdate(taxon);\r
+        RuntimeException securityException = null;\r
 \r
-        taxon =(Taxon) taxonService.load(UUID.fromString("bc09aca6-06fd-4905-b1e7-cbf7cc65d783"));\r
+        Taxon taxon = (Taxon)taxonService.load(ACHERONTIA_LACHESIS_UUID);\r
 \r
         TaxonDescription description = TaxonDescription.NewInstance(taxon);\r
         description.setTitleCache("test");\r
-        descriptionService.saveOrUpdate(description);\r
-        commitAndStartNewTransaction(null);\r
-        taxon = (Taxon)taxonService.load(UUID.fromString("bc09aca6-06fd-4905-b1e7-cbf7cc65d783"));\r
+        try {\r
+            descriptionService.saveOrUpdate(description);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException = findSecurityRuntimeException(e);\r
+            logger.debug("Expected failure of evaluation.", securityException);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+        /*\r
+         * Expectation:\r
+         * The user should not be granted to add the Description to a taxon\r
+         */\r
+        Assert.assertNotNull("evaluation should fail since the user is not permitted to edit Taxa", securityException);\r
+        taxon = (Taxon)taxonService.load(ACHERONTIA_LACHESIS_UUID);\r
         assertTrue(taxon.getDescriptions().contains(description));\r
+    }\r
 \r
+    @Test\r
+    public void testCreateDescriptionWithElement(){\r
+\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        authentication = authenticationManager.authenticate(tokenForDescriptionEditor);\r
+        context.setAuthentication(authentication);\r
 \r
+        TaxonDescription description = null;\r
+        RuntimeException securityException = null;\r
+        Taxon taxon = (Taxon)taxonService.load(UUID_ACHERONTINII);\r
+        Assert.assertTrue("taxon must not yet have descriptions", taxon.getDescriptions().size() == 0);\r
+\r
+\r
+        // 1) test for failure - description element but no feature\r
+        description = TaxonDescription.NewInstance(taxon);\r
+        DescriptionElementBase textdataNoFeature = TextData.NewInstance();\r
+        description.addElement(textdataNoFeature);\r
+\r
+        assertTrue(permissionEvaluator.hasPermission(authentication, description, "UPDATE"));\r
+        try{\r
+            descriptionService.saveOrUpdate(description);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException = findSecurityRuntimeException(e);\r
+            logger.error("RuntimeException caught");\r
+            logger.debug("Expected failure of evaluation.", securityException);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+\r
+        Assert.assertNotNull("evaluation should fail", securityException);\r
+        taxon = (Taxon)taxonService.load(UUID_ACHERONTINII);\r
+        Set<TaxonDescription> descriptions = taxon.getDescriptions();\r
+        assertTrue("taxon must not have any description", descriptions.size() == 0);\r
 \r
     }\r
 \r
     @Test\r
-    public void testCascadingInSpring(){\r
-        authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("descriptionEditor", "test"));\r
+    public void testCreateDescriptionWithElementDeny_1(){\r
+\r
         SecurityContext context = SecurityContextHolder.getContext();\r
+        authentication = authenticationManager.authenticate(tokenForDescriptionEditor);\r
         context.setAuthentication(authentication);\r
 \r
-        Taxon taxon = (Taxon)taxonService.load(UUID.fromString("928a0167-98cd-4555-bf72-52116d067625"));\r
-        TaxonDescription description = TaxonDescription.NewInstance(taxon);\r
-        description.addElement(Distribution.NewInstance());\r
-        CdmPermissionEvaluator permissionEvaluator = new CdmPermissionEvaluator();\r
-        assertTrue(permissionEvaluator.hasPermission(authentication, description, "UPDATE"));\r
+        TaxonDescription description = null;\r
+        RuntimeException securityException = null;\r
+        Taxon taxon = (Taxon)taxonService.load(UUID_ACHERONTINII);\r
+        Assert.assertTrue("taxon must not yet have descriptions", taxon.getDescriptions().size() == 0);\r
 \r
-        descriptionService.saveOrUpdate(description);\r
+        // 2) test for failure  - description element but not granted feature\r
+        description = TaxonDescription.NewInstance(taxon);\r
+        DescriptionElementBase descriptionText = TextData.NewInstance(Feature.DESCRIPTION());\r
+        description.addElement(descriptionText);\r
 \r
-        taxon = (Taxon)taxonService.load(UUID.fromString("928a0167-98cd-4555-bf72-52116d067625"));\r
+        securityException = null;\r
+        assertTrue(permissionEvaluator.hasPermission(authentication, description, "UPDATE"));\r
+        try{\r
+            descriptionService.saveOrUpdate(description);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException = findSecurityRuntimeException(e);\r
+            logger.debug("Expected failure of evaluation.", securityException);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+\r
+        Assert.assertNotNull("evaluation should fail", securityException);\r
+        taxon = (Taxon)taxonService.load(UUID_ACHERONTINII);\r
         Set<TaxonDescription> descriptions = taxon.getDescriptions();\r
-        assertTrue(descriptions.contains(description));\r
-\r
+        assertTrue("taxon must not have any description", descriptions.size() == 0);\r
 \r
     }\r
 \r
     @Test\r
-    public void testSaveSynonym(){\r
-        authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("taxonomist", "test4"));\r
+    public void testCreateDescriptionWithElementDeny_2(){\r
+\r
         SecurityContext context = SecurityContextHolder.getContext();\r
+        authentication = authenticationManager.authenticate(tokenForDescriptionEditor);\r
         context.setAuthentication(authentication);\r
 \r
-        Synonym syn = Synonym.NewInstance(BotanicalName.NewInstance(Rank.SPECIES()), null);\r
-        taxonService.saveOrUpdate(syn);\r
+        TaxonDescription description = null;\r
+        RuntimeException securityException = null;\r
+        Taxon taxon = (Taxon)taxonService.load(UUID_ACHERONTINII);\r
+        Assert.assertTrue("taxon must not yet have descriptions", taxon.getDescriptions().size() == 0);\r
 \r
-    }\r
+        // 3) test for failure\r
+        description = TaxonDescription.NewInstance(taxon);\r
+        DescriptionElementBase ecologyText = TextData.NewInstance(Feature.ECOLOGY());\r
+        description.addElement(ecologyText);\r
 \r
-    @Test(expected= EvaluationFailedException.class)\r
-    public void testEditPartOfClassification(){\r
+        securityException = null;\r
+        assertTrue(permissionEvaluator.hasPermission(authentication, description, "UPDATE"));\r
+        try{\r
+            descriptionService.saveOrUpdate(description);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException = findSecurityRuntimeException(e);\r
+            logger.error("Unexpected failure of evaluation.", e);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+\r
+        Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);\r
+        taxon = (Taxon)taxonService.load(UUID_ACHERONTINII);\r
+        Set<TaxonDescription> descriptions = taxon.getDescriptions();\r
+        assertTrue("taxon must now have one description", descriptions.size() == 1);\r
+        assertTrue("description should have one description element", descriptions.iterator().next().getElements().size() == 1);\r
+    }\r
 \r
+    @Test\r
+    public void testSaveSynonymAllow(){\r
 \r
-        authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("partEditor", "test4"));\r
         SecurityContext context = SecurityContextHolder.getContext();\r
-        context.setAuthentication(authentication);\r
+        RuntimeException securityException = null;\r
 \r
-        TaxonNode node = taxonNodeService.load(UUID.fromString("20c8f083-5870-4cbd-bf56-c5b2b98ab6a7"));\r
+        // 1) test for success\r
+        authentication = authenticationManager.authenticate(tokenForTaxonomist);\r
+        context.setAuthentication(authentication);\r
 \r
-        node = node.addChildTaxon(Taxon.NewInstance(BotanicalName.NewInstance(Rank.SPECIES()), null), null, null, null);\r
-        taxonNodeService.saveOrUpdate(node);\r
+        Synonym syn = Synonym.NewInstance(BotanicalName.NewInstance(Rank.SPECIES()), null);\r
+        UUID synUuid = UUID.randomUUID();\r
+        syn.setUuid(synUuid);\r
+        try{\r
+            taxonService.saveOrUpdate(syn);\r
+            logger.debug("will commit ...");\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException = findSecurityRuntimeException(e);\r
+            logger.error("Unexpected failure of evaluation.", e);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+        Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);\r
+        Assert.assertNotNull("The new Synonym must be persited", taxonService.find(synUuid));\r
+    }\r
 \r
-        node = taxonNodeService.load(UUID.fromString("cecfa77f-f26a-4476-9d87-a8d993cb55d9"));\r
-        node = node.addChildTaxon(Taxon.NewInstance(BotanicalName.NewInstance(Rank.GENUS()), null), null, null, null);\r
-        taxonNodeService.saveOrUpdate(node);\r
+    @Test\r
+    public void testSaveSynonymDenial(){\r
 \r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        RuntimeException securityException = null;\r
+        // 2) test for denial\r
+        authentication = authenticationManager.authenticate(tokenForDescriptionEditor);\r
+        context.setAuthentication(authentication);\r
+        securityException = null;\r
+        Synonym syn = Synonym.NewInstance(BotanicalName.NewInstance(Rank.SPECIES()), null);\r
+        UUID synUuid = syn.getUuid();\r
+        try{\r
+            taxonService.saveOrUpdate(syn);\r
+            logger.debug("will commit ...");\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException = findSecurityRuntimeException(e);\r
+            logger.debug("Expected failure of evaluation: " + securityException.getClass());\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+\r
+        Assert.assertNotNull("evaluation must fail since the user is not permitted", securityException);\r
+        Assert.assertNull("The Synonym must not be persited", taxonService.find(synUuid));\r
     }\r
 \r
-    public static void main(String[] args){\r
-        Md5PasswordEncoder encoder =new Md5PasswordEncoder();\r
+    @Test\r
+    public void testEditPartOfClassificationAllow(){\r
 \r
-        ReflectionSaltSource saltSource = new ReflectionSaltSource();\r
-        saltSource.setUserPropertyToUse("getUsername");\r
-        User user = User.NewInstance("taxonomist", "test4");\r
-        System.err.println(encoder.encodePassword("test4", saltSource.getSalt(user)));\r
+        authentication = authenticationManager.authenticate(tokenForPartEditor);\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        context.setAuthentication(authentication);\r
+        RuntimeException securityException = null;\r
+\r
+        // test for success\r
+        TaxonNode acherontia_node = taxonNodeService.load(ACHERONTIA_NODE_UUID);\r
+        long numOfChildNodes = acherontia_node.getChildNodes().size();\r
+        TaxonNode childNode = acherontia_node.addChildTaxon(Taxon.NewInstance(BotanicalName.NewInstance(Rank.SPECIES()), null), null, null, null);\r
+\r
+        try{\r
+            taxonNodeService.saveOrUpdate(acherontia_node);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException = findSecurityRuntimeException(e);\r
+            logger.error("Unexpected failure of evaluation.", securityException);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+\r
+        acherontia_node = taxonNodeService.load(ACHERONTIA_NODE_UUID);\r
+        Assert.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + (securityException != null ? securityException.getMessage() : ""), securityException);\r
+        Assert.assertEquals("the acherontia_node must now have one more child node ", numOfChildNodes + 1 , acherontia_node.getChildNodes().size());\r
     }\r
 \r
+    @Test\r
+    public void testEditPartOfClassificationDeny(){\r
 \r
+        authentication = authenticationManager.authenticate(tokenForPartEditor);\r
+        SecurityContext context = SecurityContextHolder.getContext();\r
+        context.setAuthentication(authentication);\r
+        RuntimeException securityException = null;\r
+\r
+        // test for denial\r
+        securityException = null;\r
+        TaxonNode acherontiini_node = taxonNodeService.load(ACHERONTIINI_NODE_UUID);\r
+        int numOfChildNodes = acherontiini_node.getCountChildren();\r
+        acherontiini_node.addChildTaxon(Taxon.NewInstance(BotanicalName.NewInstance(Rank.GENUS()), null), null, null, null);\r
+\r
+        try{\r
+            logger.debug("==============================");\r
+            taxonNodeService.saveOrUpdate(acherontiini_node);\r
+            commitAndStartNewTransaction(null);\r
+        } catch (RuntimeException e){\r
+            securityException = findSecurityRuntimeException(e);\r
+            logger.debug("Expected failure of evaluation.", securityException);\r
+        } finally {\r
+            // needed in case saveOrUpdate was interrupted by the RuntimeException\r
+            // commitAndStartNewTransaction() would raise an UnexpectedRollbackException\r
+            endTransaction();\r
+            startNewTransaction();\r
+        }\r
+\r
+        acherontiini_node = taxonNodeService.load(ACHERONTIINI_NODE_UUID);\r
+        Assert.assertNotNull("evaluation must fail since the user is not permitted", securityException);\r
+        Assert.assertEquals("the number of child nodes must be unchanged ", numOfChildNodes , acherontiini_node.getChildNodes().size());\r
 \r
+    }\r
 \r
 }\r