more security testing
[cdmlib.git] / cdmlib-services / src / test / java / eu / etaxonomy / cdm / api / service / SecurityTest.java
1 /**
2 * Copyright (C) 2011 EDIT
3 * European Distributed Institute of Taxonomy
4 * http://www.e-taxonomy.eu
5 *
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.
8 */
9 package eu.etaxonomy.cdm.api.service;
10
11 import static org.junit.Assert.assertEquals;
12 import static org.junit.Assert.assertFalse;
13 import static org.junit.Assert.assertTrue;
14
15
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.UUID;
22
23 import javax.sql.DataSource;
24
25 import org.apache.log4j.Logger;
26
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;
32
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;
48
49
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;
58
59
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;
66
67
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;
73
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;
78
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;
88
89
90 @DataSet
91 public class SecurityTest extends CdmTransactionalIntegrationTestWithSecurity{
92
93 private static final UUID PART_EDITOR_UUID = UUID.fromString("38a251bd-0ba4-426f-8fcb-5c09560749a7");
94
95 private static final String PASSWORD_TAXON_EDITOR = "test2";
96
97 private static final String PASSWORD_ADMIN = "sPePhAz6";
98
99 private static final UUID ACHERONTIA_NODE_UUID = UUID.fromString("20c8f083-5870-4cbd-bf56-c5b2b98ab6a7");
100
101 private static final UUID ACHERONTIINI_NODE_UUID = UUID.fromString("cecfa77f-f26a-4476-9d87-a8d993cb55d9");
102
103 private static final UUID ACHERONTIA_LACHESIS_UUID = UUID.fromString("bc09aca6-06fd-4905-b1e7-cbf7cc65d783");
104
105 private static final Logger logger = Logger.getLogger(SecurityTest.class);
106
107 /**
108 * The transaction manager to use
109 */
110 @SpringBeanByType
111 PlatformTransactionManager transactionManager;
112
113 @SpringBeanByType
114 private ITaxonService taxonService;
115
116 @SpringBeanByType
117 private ITaxonNodeService taxonNodeService;
118
119 @SpringBeanByType
120 private IDescriptionService descriptionService;
121
122 @SpringBeanByType
123 private IUserService userService;
124
125
126 @TestDataSource
127 protected DataSource dataSource;
128
129 private Authentication authentication;
130
131 @SpringBeanByType
132 private AuthenticationManager authenticationManager;
133
134 @SpringBeanByType
135 private SaltSource saltSource;
136
137 @SpringBeanByType
138 private PasswordEncoder passwordEncoder;
139
140 private UsernamePasswordAuthenticationToken tokenForAdmin;
141
142 private UsernamePasswordAuthenticationToken tokenForTaxonEditor;
143
144 private UsernamePasswordAuthenticationToken tokenForDescriptionEditor;
145
146 private UsernamePasswordAuthenticationToken tokenForPartEditor;
147
148 private UsernamePasswordAuthenticationToken tokenForTaxonomist;
149
150
151
152 @Before
153 public void setUp(){
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");
159 }
160
161 /**
162 * no assertions in this test, since it is only used to create password hashes for test data
163 */
164 @Test
165 public void testEncryptPassword(){
166
167 String password = PASSWORD_ADMIN;
168 User user = User.NewInstance("admin", "");
169
170 Object salt = this.saltSource.getSalt(user);
171 String passwordEncrypted = passwordEncoder.encodePassword(password, salt);
172 logger.info("encrypted password: " + passwordEncrypted );
173 }
174
175 /**
176 * Test method for {@link eu.etaxonomy.cdm.api.service.TaxonServiceImpl#saveTaxon(eu.etaxonomy.cdm.model.taxon.TaxonBase)}.
177 */
178 @Test
179 public final void testSaveTaxon() {
180 /*
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)));
186
187 */
188 authentication = authenticationManager.authenticate(tokenForAdmin);
189 SecurityContext context = SecurityContextHolder.getContext();
190 context.setAuthentication(authentication);
191
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);
197
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);
203
204 }
205
206 @Test
207 public void testChangeOwnPassword(){
208
209 SecurityContext context = SecurityContextHolder.getContext();
210 // authenticate as admin
211 authentication = authenticationManager.authenticate(tokenForTaxonEditor);
212 context.setAuthentication(authentication);
213
214 // User currentUser = (User) context.getAuthentication().getPrincipal();
215
216 String newPass = "poiweorijo";
217 userService.changePassword(PASSWORD_TAXON_EDITOR, newPass);
218 commitAndStartNewTransaction(null);
219
220 // try to re-authenticate user with changed password
221 UsernamePasswordAuthenticationToken newTokenForTaxonEditor = new UsernamePasswordAuthenticationToken("taxonEditor", newPass);
222 authentication = authenticationManager.authenticate(newTokenForTaxonEditor);
223 }
224
225 @Test
226 @Ignore // second part fails; changePasswordForUser seems unprotected !!
227 public void testChangeOthersPassword(){
228
229 SecurityContext context = SecurityContextHolder.getContext();
230 // (1) authenticate as admin
231 authentication = authenticationManager.authenticate(tokenForAdmin);
232 context.setAuthentication(authentication);
233
234 EvaluationFailedException evaluationFailedException = null;
235 try{
236 userService.changePasswordForUser("taxonomist", "zuaisd");
237 commitAndStartNewTransaction(null);
238 } catch (RuntimeException e){
239 evaluationFailedException = findEvaluationFailedExceptionIn(e);
240 logger.debug("Unexpected failure of evaluation.", evaluationFailedException);
241 }
242 Assert.assertNull("must not fail here!", evaluationFailedException);
243
244 // ok, now try authenticating partEditor with new password
245 UsernamePasswordAuthenticationToken newToken = new UsernamePasswordAuthenticationToken("partEditor", "poiweorijo");
246 authentication = authenticationManager.authenticate(newToken);
247
248 // (2) authenticate as under privileged user - not an admin !!!
249 authentication = authenticationManager.authenticate(tokenForDescriptionEditor);
250 context.setAuthentication(authentication);
251
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());
257 }
258 // finally perform the test :
259 try{
260 userService.changePasswordForUser("partEditor", "poiweorijo");
261 commitAndStartNewTransaction(null);
262 } catch (RuntimeException e){
263 evaluationFailedException = findEvaluationFailedExceptionIn(e);
264 }
265 Assert.assertNotNull("must fail here!", evaluationFailedException);
266 }
267
268 @Test
269 public void testUpdateUser(){
270
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);
277
278 userService.createUser(user);
279 user.setEmailAddress("test@bgbm.org");
280
281 userService.updateUser(user);
282 userService.update(user);
283 userService.saveOrUpdate(user);
284
285 }
286
287 @Test
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);
296
297 actualTaxon.setName(BotanicalName.NewInstance(Rank.SPECIES()));
298 taxonService.saveOrUpdate(actualTaxon);
299
300 authentication = authenticationManager.authenticate(tokenForTaxonEditor);
301 context = SecurityContextHolder.getContext();
302 context.setAuthentication(authentication);
303 actualTaxon = taxonService.load(uuid);
304
305 actualTaxon.setDoubtful(true);
306 taxonService.saveOrUpdate(actualTaxon);
307
308 }
309
310 @Test
311 public void testCascadingInSpringSecurityAccesDenied(){
312 /*authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken("partEditor", "test4"));
313 SecurityContext context = SecurityContextHolder.getContext();
314 context.setAuthentication(authentication);
315 */
316 authentication = authenticationManager.authenticate(tokenForTaxonEditor);
317 SecurityContext context = SecurityContextHolder.getContext();
318 context.setAuthentication(authentication);
319 CdmPermissionEvaluator permissionEvaluator = new CdmPermissionEvaluator();
320
321 Taxon taxon =(Taxon) taxonService.load(ACHERONTIA_LACHESIS_UUID);
322 taxon.setDoubtful(false);
323 assertTrue(permissionEvaluator.hasPermission(authentication, taxon, "UPDATE"));
324 taxonService.save(taxon);
325 taxon = null;
326 commitAndStartNewTransaction(null);
327
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);
331
332 authentication = authenticationManager.authenticate(tokenForDescriptionEditor);
333 context = SecurityContextHolder.getContext();
334 context.setAuthentication(authentication);
335
336 //taxonService.saveOrUpdate(taxon);
337
338 taxon =(Taxon) taxonService.load(ACHERONTIA_LACHESIS_UUID);
339
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));
346 }
347
348 @Test
349 public void testCascadingInSpring(){
350 authentication = authenticationManager.authenticate(tokenForDescriptionEditor);
351 SecurityContext context = SecurityContextHolder.getContext();
352 context.setAuthentication(authentication);
353
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"));
359
360 descriptionService.saveOrUpdate(description);
361
362 taxon = (Taxon)taxonService.load(UUID.fromString("928a0167-98cd-4555-bf72-52116d067625"));
363 Set<TaxonDescription> descriptions = taxon.getDescriptions();
364 assertTrue(descriptions.contains(description));
365
366 }
367
368 @Test
369 public void testSaveSynonym(){
370 authentication = authenticationManager.authenticate(tokenForTaxonomist);
371 SecurityContext context = SecurityContextHolder.getContext();
372 context.setAuthentication(authentication);
373
374 Synonym syn = Synonym.NewInstance(BotanicalName.NewInstance(Rank.SPECIES()), null);
375 taxonService.saveOrUpdate(syn);
376 }
377
378 @Test
379 @Ignore //FIXME test must not fail !!!!!
380 public void testEditPartOfClassification(){
381 /*
382 * the user 'partEditor' has the following authorities:
383 *
384 * - TAXONNODE.CREATE{20c8f083-5870-4cbd-bf56-c5b2b98ab6a7}
385 * - TAXONNODE.UPDATE{20c8f083-5870-4cbd-bf56-c5b2b98ab6a7}
386 *
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.
389 */
390
391 authentication = authenticationManager.authenticate(tokenForPartEditor);
392 SecurityContext context = SecurityContextHolder.getContext();
393 context.setAuthentication(authentication);
394
395 // test for success
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;
400 try{
401 taxonNodeService.saveOrUpdate(acherontia_node);
402 commitAndStartNewTransaction(null);
403 } catch (RuntimeException e){
404 evaluationFailedException = findEvaluationFailedExceptionIn(e);
405 logger.debug("Unexpected failure of evaluation.", evaluationFailedException);
406 }
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());
409
410 // test for denial
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);
415 try{
416 taxonNodeService.saveOrUpdate(acherontiini_node);
417 commitAndStartNewTransaction(null);
418 } catch (RuntimeException e){
419 evaluationFailedException = findEvaluationFailedExceptionIn(e);
420 }
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());
423
424 }
425
426 public static void main(String[] args){
427 Md5PasswordEncoder encoder =new Md5PasswordEncoder();
428
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)));
433 }
434
435
436
437
438 }