2 * Copyright (C) 2011 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
6 * The contents of this file are subject to the Mozilla Public License Version 1.1
7 * See LICENSE.TXT at the top of this package for the full license terms.
9 package eu
.etaxonomy
.cdm
.api
.service
;
11 import static org
.junit
.Assert
.assertEquals
;
12 import static org
.junit
.Assert
.assertFalse
;
13 import static org
.junit
.Assert
.assertTrue
;
16 import java
.util
.ArrayList
;
17 import java
.util
.Collection
;
18 import java
.util
.Iterator
;
19 import java
.util
.List
;
21 import java
.util
.UUID
;
23 import javax
.sql
.DataSource
;
25 import org
.apache
.log4j
.Logger
;
27 import org
.junit
.Assert
;
28 import org
.junit
.Before
;
29 import org
.junit
.Ignore
;
30 import org
.junit
.Test
;
31 import org
.junit
.runner
.RunWith
;
33 import org
.springframework
.beans
.factory
.annotation
.Autowired
;
34 import org
.springframework
.orm
.hibernate3
.HibernateSystemException
;
35 import org
.springframework
.security
.access
.vote
.RoleVoter
;
36 import org
.springframework
.security
.authentication
.AuthenticationManager
;
37 import org
.springframework
.security
.authentication
.UsernamePasswordAuthenticationToken
;
38 import org
.springframework
.security
.authentication
.dao
.ReflectionSaltSource
;
39 import org
.springframework
.security
.authentication
.dao
.SaltSource
;
40 import org
.springframework
.security
.authentication
.encoding
.Md5PasswordEncoder
;
41 import org
.springframework
.security
.authentication
.encoding
.PasswordEncoder
;
42 import org
.springframework
.security
.core
.Authentication
;
43 import org
.springframework
.security
.core
.GrantedAuthority
;
44 import org
.springframework
.security
.core
.context
.SecurityContext
;
45 import org
.springframework
.security
.core
.context
.SecurityContextHolder
;
46 import org
.springframework
.test
.annotation
.ExpectedException
;
47 import org
.springframework
.transaction
.PlatformTransactionManager
;
50 import org
.unitils
.database
.annotations
.Transactional
;
51 import org
.unitils
.UnitilsJUnit4TestClassRunner
;
52 import org
.unitils
.database
.annotations
.TestDataSource
;
53 import org
.unitils
.database
.util
.TransactionMode
;
54 import org
.unitils
.dbunit
.annotation
.DataSet
;
55 import org
.unitils
.spring
.annotation
.SpringApplicationContext
;
56 import org
.unitils
.spring
.annotation
.SpringBeanByName
;
57 import org
.unitils
.spring
.annotation
.SpringBeanByType
;
60 import eu
.etaxonomy
.cdm
.api
.service
.config
.IFindTaxaAndNamesConfigurator
;
61 import eu
.etaxonomy
.cdm
.api
.service
.config
.FindTaxaAndNamesConfiguratorImpl
;
62 import eu
.etaxonomy
.cdm
.api
.service
.pager
.Pager
;
63 import eu
.etaxonomy
.cdm
.database
.EvaluationFailedException
;
64 import eu
.etaxonomy
.cdm
.model
.common
.Language
;
65 import eu
.etaxonomy
.cdm
.model
.common
.User
;
68 import eu
.etaxonomy
.cdm
.model
.description
.DescriptionElementBase
;
69 import eu
.etaxonomy
.cdm
.model
.description
.Distribution
;
70 import eu
.etaxonomy
.cdm
.model
.description
.Feature
;
71 import eu
.etaxonomy
.cdm
.model
.description
.TaxonDescription
;
72 import eu
.etaxonomy
.cdm
.model
.description
.TextData
;
74 import eu
.etaxonomy
.cdm
.model
.media
.Media
;
75 import eu
.etaxonomy
.cdm
.model
.name
.BotanicalName
;
76 import eu
.etaxonomy
.cdm
.model
.name
.Rank
;
77 import eu
.etaxonomy
.cdm
.model
.taxon
.Synonym
;
79 import eu
.etaxonomy
.cdm
.model
.taxon
.SynonymRelationshipType
;
80 import eu
.etaxonomy
.cdm
.model
.taxon
.Taxon
;
81 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonBase
;
82 import eu
.etaxonomy
.cdm
.model
.taxon
.TaxonNode
;
83 import eu
.etaxonomy
.cdm
.persistence
.dao
.BeanInitializer
;
84 import eu
.etaxonomy
.cdm
.persistence
.hibernate
.permission
.CdmPermissionEvaluator
;
85 import eu
.etaxonomy
.cdm
.test
.integration
.CdmTransactionalIntegrationTest
;
86 import eu
.etaxonomy
.cdm
.test
.integration
.CdmTransactionalIntegrationTestWithSecurity
;
87 import eu
.etaxonomy
.cdm
.test
.unitils
.CleanSweepInsertLoadStrategy
;
91 public class SecurityTest
extends CdmTransactionalIntegrationTestWithSecurity
{
93 private static final UUID PART_EDITOR_UUID
= UUID
.fromString("38a251bd-0ba4-426f-8fcb-5c09560749a7");
95 private static final String PASSWORD_TAXON_EDITOR
= "test2";
97 private static final String PASSWORD_ADMIN
= "sPePhAz6";
99 private static final UUID ACHERONTIA_NODE_UUID
= UUID
.fromString("20c8f083-5870-4cbd-bf56-c5b2b98ab6a7");
101 private static final UUID ACHERONTIINI_NODE_UUID
= UUID
.fromString("cecfa77f-f26a-4476-9d87-a8d993cb55d9");
103 private static final UUID ACHERONTIA_LACHESIS_UUID
= UUID
.fromString("bc09aca6-06fd-4905-b1e7-cbf7cc65d783");
105 private static final Logger logger
= Logger
.getLogger(SecurityTest
.class);
108 * The transaction manager to use
111 PlatformTransactionManager transactionManager
;
114 private ITaxonService taxonService
;
117 private ITaxonNodeService taxonNodeService
;
120 private IDescriptionService descriptionService
;
123 private IUserService userService
;
127 protected DataSource dataSource
;
129 private Authentication authentication
;
132 private AuthenticationManager authenticationManager
;
135 private SaltSource saltSource
;
138 private PasswordEncoder passwordEncoder
;
140 private UsernamePasswordAuthenticationToken tokenForAdmin
;
142 private UsernamePasswordAuthenticationToken tokenForTaxonEditor
;
144 private UsernamePasswordAuthenticationToken tokenForDescriptionEditor
;
146 private UsernamePasswordAuthenticationToken tokenForPartEditor
;
148 private UsernamePasswordAuthenticationToken tokenForTaxonomist
;
154 tokenForAdmin
= new UsernamePasswordAuthenticationToken("admin", PASSWORD_ADMIN
);
155 tokenForTaxonEditor
= new UsernamePasswordAuthenticationToken("taxonEditor", PASSWORD_TAXON_EDITOR
);
156 tokenForDescriptionEditor
= new UsernamePasswordAuthenticationToken("descriptionEditor", "test");
157 tokenForPartEditor
= new UsernamePasswordAuthenticationToken("partEditor", "test4");
158 tokenForTaxonomist
= new UsernamePasswordAuthenticationToken("taxonomist", "test4");
162 * no assertions in this test, since it is only used to create password hashes for test data
165 public void testEncryptPassword(){
167 String password
= PASSWORD_ADMIN
;
168 User user
= User
.NewInstance("admin", "");
170 Object salt
= this.saltSource
.getSalt(user
);
171 String passwordEncrypted
= passwordEncoder
.encodePassword(password
, salt
);
172 logger
.info("encrypted password: " + passwordEncrypted
);
176 * Test method for {@link eu.etaxonomy.cdm.api.service.TaxonServiceImpl#saveTaxon(eu.etaxonomy.cdm.model.taxon.TaxonBase)}.
179 public final void testSaveTaxon() {
181 Md5PasswordEncoder encoder =new Md5PasswordEncoder();
182 ReflectionSaltSource saltSource = new ReflectionSaltSource();
183 saltSource.setUserPropertyToUse("getUsername");
184 User user = User.NewInstance("partEditor", "test4");
185 System.err.println(encoder.encodePassword("test4", saltSource.getSalt(user)));
188 authentication
= authenticationManager
.authenticate(tokenForAdmin
);
189 SecurityContext context
= SecurityContextHolder
.getContext();
190 context
.setAuthentication(authentication
);
192 Taxon expectedTaxon
= Taxon
.NewInstance(BotanicalName
.NewInstance(Rank
.SPECIES()), null);
193 UUID uuid
= taxonService
.save(expectedTaxon
);
194 //taxonService.getSession().flush();
195 TaxonBase
<?
> actualTaxon
= taxonService
.load(uuid
);
196 assertEquals(expectedTaxon
, actualTaxon
);
198 authentication
= authenticationManager
.authenticate(tokenForTaxonEditor
);
199 context
= SecurityContextHolder
.getContext();
200 context
.setAuthentication(authentication
);
201 expectedTaxon
= Taxon
.NewInstance(BotanicalName
.NewInstance(Rank
.GENUS()), null);
202 taxonService
.saveOrUpdate(actualTaxon
);
207 public void testChangeOwnPassword(){
209 SecurityContext context
= SecurityContextHolder
.getContext();
210 // authenticate as admin
211 authentication
= authenticationManager
.authenticate(tokenForTaxonEditor
);
212 context
.setAuthentication(authentication
);
214 // User currentUser = (User) context.getAuthentication().getPrincipal();
216 String newPass
= "poiweorijo";
217 userService
.changePassword(PASSWORD_TAXON_EDITOR
, newPass
);
218 commitAndStartNewTransaction(null);
220 // try to re-authenticate user with changed password
221 UsernamePasswordAuthenticationToken newTokenForTaxonEditor
= new UsernamePasswordAuthenticationToken("taxonEditor", newPass
);
222 authentication
= authenticationManager
.authenticate(newTokenForTaxonEditor
);
226 @Ignore // second part fails; changePasswordForUser seems unprotected !!
227 public void testChangeOthersPassword(){
229 SecurityContext context
= SecurityContextHolder
.getContext();
230 // (1) authenticate as admin
231 authentication
= authenticationManager
.authenticate(tokenForAdmin
);
232 context
.setAuthentication(authentication
);
234 EvaluationFailedException evaluationFailedException
= null;
236 userService
.changePasswordForUser("taxonomist", "zuaisd");
237 commitAndStartNewTransaction(null);
238 } catch (RuntimeException e
){
239 evaluationFailedException
= findEvaluationFailedExceptionIn(e
);
240 logger
.debug("Unexpected failure of evaluation.", evaluationFailedException
);
242 Assert
.assertNull("must not fail here!", evaluationFailedException
);
244 // ok, now try authenticating partEditor with new password
245 UsernamePasswordAuthenticationToken newToken
= new UsernamePasswordAuthenticationToken("partEditor", "poiweorijo");
246 authentication
= authenticationManager
.authenticate(newToken
);
248 // (2) authenticate as under privileged user - not an admin !!!
249 authentication
= authenticationManager
.authenticate(tokenForDescriptionEditor
);
250 context
.setAuthentication(authentication
);
252 // check test preconditions user name and authorities
253 Assert
.assertEquals("descriptionEditor", context
.getAuthentication().getName());
254 Collection
<GrantedAuthority
> authorities
= context
.getAuthentication().getAuthorities();
255 for(GrantedAuthority authority
: authorities
){
256 Assert
.assertNotSame("user must not have authority 'ALL.ADMIN'", "ALL.ADMIN", authority
.getAuthority());
258 // finally perform the test :
260 userService
.changePasswordForUser("partEditor", "poiweorijo");
261 commitAndStartNewTransaction(null);
262 } catch (RuntimeException e
){
263 evaluationFailedException
= findEvaluationFailedExceptionIn(e
);
265 Assert
.assertNotNull("must fail here!", evaluationFailedException
);
269 public void testUpdateUser(){
271 authentication
= authenticationManager
.authenticate(tokenForAdmin
);
272 SecurityContext context
= SecurityContextHolder
.getContext();
273 context
.setAuthentication(authentication
);
274 String username
= "standardUser";
275 String password
= "pw";
276 User user
= User
.NewInstance(username
, password
);
278 userService
.createUser(user
);
279 user
.setEmailAddress("test@bgbm.org");
281 userService
.updateUser(user
);
282 userService
.update(user
);
283 userService
.saveOrUpdate(user
);
288 public final void testSaveOrUpdateTaxon() {
289 authentication
= authenticationManager
.authenticate(tokenForAdmin
);
290 SecurityContext context
= SecurityContextHolder
.getContext();
291 context
.setAuthentication(authentication
);
292 Taxon expectedTaxon
= Taxon
.NewInstance(null, null);
293 UUID uuid
= taxonService
.save(expectedTaxon
);
294 TaxonBase
<?
> actualTaxon
= taxonService
.load(uuid
);
295 assertEquals(expectedTaxon
, actualTaxon
);
297 actualTaxon
.setName(BotanicalName
.NewInstance(Rank
.SPECIES()));
298 taxonService
.saveOrUpdate(actualTaxon
);
300 authentication
= authenticationManager
.authenticate(tokenForTaxonEditor
);
301 context
= SecurityContextHolder
.getContext();
302 context
.setAuthentication(authentication
);
303 actualTaxon
= taxonService
.load(uuid
);
305 actualTaxon
.setDoubtful(true);
306 taxonService
.saveOrUpdate(actualTaxon
);
311 public void testCascadingInSpringSecurityAccesDenied(){
312 /*authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("partEditor", "test4"));
313 SecurityContext context = SecurityContextHolder.getContext();
314 context.setAuthentication(authentication);
316 authentication
= authenticationManager
.authenticate(tokenForTaxonEditor
);
317 SecurityContext context
= SecurityContextHolder
.getContext();
318 context
.setAuthentication(authentication
);
319 CdmPermissionEvaluator permissionEvaluator
= new CdmPermissionEvaluator();
321 Taxon taxon
=(Taxon
) taxonService
.load(ACHERONTIA_LACHESIS_UUID
);
322 taxon
.setDoubtful(false);
323 assertTrue(permissionEvaluator
.hasPermission(authentication
, taxon
, "UPDATE"));
324 taxonService
.save(taxon
);
326 commitAndStartNewTransaction(null);
328 //during cascading the permissions are not evaluated, but with hibernate listener every database transaction can be interrupted, but how to manage it,
329 //when someone has the rights to save descriptions, but not taxa (the editor always saves everything by saving the taxon)
330 //taxonService.saveOrUpdate(taxon);
332 authentication
= authenticationManager
.authenticate(tokenForDescriptionEditor
);
333 context
= SecurityContextHolder
.getContext();
334 context
.setAuthentication(authentication
);
336 //taxonService.saveOrUpdate(taxon);
338 taxon
=(Taxon
) taxonService
.load(ACHERONTIA_LACHESIS_UUID
);
340 TaxonDescription description
= TaxonDescription
.NewInstance(taxon
);
341 description
.setTitleCache("test");
342 descriptionService
.saveOrUpdate(description
);
343 commitAndStartNewTransaction(null);
344 taxon
= (Taxon
)taxonService
.load(ACHERONTIA_LACHESIS_UUID
);
345 assertTrue(taxon
.getDescriptions().contains(description
));
349 public void testCascadingInSpring(){
350 authentication
= authenticationManager
.authenticate(tokenForDescriptionEditor
);
351 SecurityContext context
= SecurityContextHolder
.getContext();
352 context
.setAuthentication(authentication
);
354 Taxon taxon
= (Taxon
)taxonService
.load(UUID
.fromString("928a0167-98cd-4555-bf72-52116d067625"));
355 TaxonDescription description
= TaxonDescription
.NewInstance(taxon
);
356 description
.addElement(Distribution
.NewInstance());
357 CdmPermissionEvaluator permissionEvaluator
= new CdmPermissionEvaluator();
358 assertTrue(permissionEvaluator
.hasPermission(authentication
, description
, "UPDATE"));
360 descriptionService
.saveOrUpdate(description
);
362 taxon
= (Taxon
)taxonService
.load(UUID
.fromString("928a0167-98cd-4555-bf72-52116d067625"));
363 Set
<TaxonDescription
> descriptions
= taxon
.getDescriptions();
364 assertTrue(descriptions
.contains(description
));
369 public void testSaveSynonym(){
370 authentication
= authenticationManager
.authenticate(tokenForTaxonomist
);
371 SecurityContext context
= SecurityContextHolder
.getContext();
372 context
.setAuthentication(authentication
);
374 Synonym syn
= Synonym
.NewInstance(BotanicalName
.NewInstance(Rank
.SPECIES()), null);
375 taxonService
.saveOrUpdate(syn
);
379 @Ignore //FIXME test must not fail !!!!!
380 public void testEditPartOfClassification(){
382 * the user 'partEditor' has the following authorities:
384 * - TAXONNODE.CREATE{20c8f083-5870-4cbd-bf56-c5b2b98ab6a7}
385 * - TAXONNODE.UPDATE{20c8f083-5870-4cbd-bf56-c5b2b98ab6a7}
387 * that is 'partEditor' is granted to edit the subtree of
388 * which ACHERONTIA_NODE_UUID [20c8f083-5870-4cbd-bf56-c5b2b98ab6a7] is the root node.
391 authentication
= authenticationManager
.authenticate(tokenForPartEditor
);
392 SecurityContext context
= SecurityContextHolder
.getContext();
393 context
.setAuthentication(authentication
);
396 TaxonNode acherontia_node
= taxonNodeService
.load(ACHERONTIA_NODE_UUID
);
397 long numOfChildNodes
= acherontia_node
.getChildNodes().size();
398 TaxonNode childNode
= acherontia_node
.addChildTaxon(Taxon
.NewInstance(BotanicalName
.NewInstance(Rank
.SPECIES()), null), null, null, null);
399 EvaluationFailedException evaluationFailedException
= null;
401 taxonNodeService
.saveOrUpdate(acherontia_node
);
402 commitAndStartNewTransaction(null);
403 } catch (RuntimeException e
){
404 evaluationFailedException
= findEvaluationFailedExceptionIn(e
);
405 logger
.debug("Unexpected failure of evaluation.", evaluationFailedException
);
407 Assert
.assertNull("evaluation must not fail since the user is permitted, CAUSE :" + evaluationFailedException
.getMessage(), evaluationFailedException
);
408 Assert
.assertEquals("the acherontia_node must now have one more child node ", numOfChildNodes
+ 1 , acherontia_node
.getChildNodes().size());
411 evaluationFailedException
= null;
412 TaxonNode acherontiini_node
= taxonNodeService
.load(ACHERONTIINI_NODE_UUID
);
413 numOfChildNodes
= acherontiini_node
.getCountChildren();
414 acherontiini_node
.addChildTaxon(Taxon
.NewInstance(BotanicalName
.NewInstance(Rank
.GENUS()), null), null, null, null);
416 taxonNodeService
.saveOrUpdate(acherontiini_node
);
417 commitAndStartNewTransaction(null);
418 } catch (RuntimeException e
){
419 evaluationFailedException
= findEvaluationFailedExceptionIn(e
);
421 Assert
.assertNotNull("evaluation must fail since the user is not permitted", evaluationFailedException
);
422 Assert
.assertEquals("the number of child nodes must be unchanged ", numOfChildNodes
, acherontiini_node
.getChildNodes().size());
426 public static void main(String
[] args
){
427 Md5PasswordEncoder encoder
=new Md5PasswordEncoder();
429 ReflectionSaltSource saltSource
= new ReflectionSaltSource();
430 saltSource
.setUserPropertyToUse("getUsername");
431 User user
= User
.NewInstance("taxonomist", "test4");
432 System
.err
.println(encoder
.encodePassword("test4", saltSource
.getSalt(user
)));