Add IEventBaseService to application controller #5403
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / application / FirstDataInserter.java
1 // $Id$
2 /**
3 * Copyright (C) 2012 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10 package eu.etaxonomy.cdm.api.application;
11
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.Collection;
15 import java.util.HashSet;
16 import java.util.List;
17 import java.util.Set;
18 import java.util.UUID;
19
20 import org.apache.log4j.Logger;
21 import org.springframework.beans.factory.annotation.Autowired;
22 import org.springframework.context.ApplicationContext;
23 import org.springframework.context.ApplicationListener;
24 import org.springframework.context.event.ContextRefreshedEvent;
25 import org.springframework.context.event.ContextStartedEvent;
26 import org.springframework.security.access.intercept.RunAsUserToken;
27 import org.springframework.security.authentication.AnonymousAuthenticationToken;
28 import org.springframework.security.authentication.AuthenticationProvider;
29 import org.springframework.security.core.Authentication;
30 import org.springframework.security.core.GrantedAuthority;
31 import org.springframework.security.core.context.SecurityContext;
32 import org.springframework.security.core.context.SecurityContextHolder;
33 import org.springframework.transaction.PlatformTransactionManager;
34 import org.springframework.transaction.TransactionDefinition;
35 import org.springframework.transaction.TransactionStatus;
36 import org.springframework.transaction.support.DefaultTransactionDefinition;
37
38 import eu.etaxonomy.cdm.api.service.ICommonService;
39 import eu.etaxonomy.cdm.api.service.IGrantedAuthorityService;
40 import eu.etaxonomy.cdm.api.service.IGroupService;
41 import eu.etaxonomy.cdm.api.service.IUserService;
42 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
43 import eu.etaxonomy.cdm.common.monitor.NullProgressMonitor;
44 import eu.etaxonomy.cdm.config.Configuration;
45 import eu.etaxonomy.cdm.model.common.GrantedAuthorityImpl;
46 import eu.etaxonomy.cdm.model.common.Group;
47 import eu.etaxonomy.cdm.model.common.User;
48 import eu.etaxonomy.cdm.model.metadata.CdmMetaData;
49 import eu.etaxonomy.cdm.persistence.hibernate.permission.Role;
50 import eu.etaxonomy.cdm.persistence.query.OrderHint;
51
52 /**
53 * The <code>FirstDataInserter</code> is responsible for equipping a new and empty database with
54 * the initial set of data need by the cdmlib. It operates not only on empty databases,
55 * its methods are executed everytime the ApplicationContext has been started up, that is listens
56 * for {@link ContextStartedEvent}s.
57 * <p>
58 * responsibilities:
59 * <ul>
60 * <li>User 'admin' and role 'ROLE_ADMIN'</li>
61 * <li>cdm metadata</li>
62 * <ul>
63 * <p>
64 * The <code>runAsAuthenticationProvider</code> must be set in a security application context, eg:
65 * {@code
66 <bean id="firstDataInserter" class="eu.etaxonomy.cdm.api.application.FirstDataInserter">
67 <property name="runAsAuthenticationProvider" ref="runAsAuthenticationProvider"/>
68 </bean>
69 }
70 *
71 *
72 *
73 * @author a.kohlbecker
74 * @date Oct 12, 2012
75 *
76 */
77 //@RunAs("ROLE_ADMIN") // seems to be broken in spring see: https://jira.springsource.org/browse/SEC-1671
78 public class FirstDataInserter implements ApplicationListener<ContextRefreshedEvent> {
79
80 public static final Logger logger = Logger.getLogger(FirstDataInserter.class);
81
82 /**
83 * must match the key in eu/etaxonomy/cdm/services_security.xml
84 */
85 private static final String RUN_AS_KEY = "TtlCx3pgKC4l";
86
87 public static final String[] editorGroupAuthorities = new String[]{
88 "REFERENCE.[CREATE,READ]",
89 "TAXONNAMEBASE.[CREATE,READ,UPDATE]",
90 "TEAMORPERSONBASE.[CREATE,READ]",
91 "TAXONBASE.[CREATE,UPDATE,DELETE,READ]",
92 "DESCRIPTIONBASE.[CREATE,UPDATE,DELETE,READ]",
93 "DESCRIPTIONELEMENTBASE.[CREATE,UPDATE,DELETE,READ]",
94 };
95
96 public static final String[] projectManagerGroupAuthorities = new String[]{
97 "REFERENCE.[UPDATE,DELETE]",
98 "TAXONNAMEBASE.[DELETE]",
99 "TEAMORPERSONBASE.[UPDATE,DELETE]",
100 Role.ROLE_PROJECT_MANAGER.toString(),
101 };
102
103 @Autowired
104 private ICommonService commonService;
105
106 @Autowired
107 private IUserService userService;
108
109 @Autowired
110 private IGroupService groupService;
111
112
113 @Autowired
114 private IGrantedAuthorityService grantedAuthorityService;
115
116 // not to be autowired, since the FirstdataInserter must be usable without security
117 private AuthenticationProvider runAsAuthenticationProvider = null;
118
119 protected PlatformTransactionManager transactionManager;
120
121 protected DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
122
123 private IProgressMonitor progressMonitor = null;
124
125 private boolean firstDataInserted = false;
126
127 private Authentication authentication;
128
129 private ApplicationContext applicationContext;
130
131 @Autowired
132 public void setTransactionManager(PlatformTransactionManager transactionManager) {
133 this.transactionManager = transactionManager;
134 }
135
136 public FirstDataInserter() {
137 txDefinition.setName("FirstDataInserter.insertFirstData()");
138 txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
139 }
140
141 @Override
142 public void onApplicationEvent(ContextRefreshedEvent event) {
143 if(event.getApplicationContext() instanceof MonitoredGenericApplicationContext){
144 progressMonitor = ((MonitoredGenericApplicationContext)event.getApplicationContext()).getCurrentMonitor();
145 /* TODO set up work amount, currently the amount of work ticks is hard coded
146 * in {@link CdmApplicationControllersetNewDataSource}, but we need some means to register
147 * additional ticks.
148 * see http://dev.e-taxonomy.eu/trac/ticket/3140 (generic way to obtain work ticks of application startup for monitoring)
149 *
150 */
151 } else {
152 progressMonitor = new NullProgressMonitor();
153 }
154 applicationContext = event.getApplicationContext();
155
156 insertFirstData();
157 }
158
159
160 private void insertFirstData() {
161
162 // this ApplicationListener may be called multiple times in nested
163 // application contexts like in web applications
164 if(!firstDataInserted){
165
166 runAsAuthentication();
167
168 TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);
169
170 logger.info("inserting first data");
171 checkAdminUser();
172 checkDefaultGroups();
173 checkMetadata();
174 firstDataInserted = true;
175
176 transactionManager.commit(txStatus);
177
178 restoreAuthentication();
179
180 } else {
181 logger.debug("insertFirstData() already executed before, skipping this time");
182 }
183 }
184
185 /**
186 * needed to work around the broken @RunAs("ROLE_ADMIN") which
187 * seems to be broken in spring see: https://jira.springsource.org/browse/SEC-1671
188 */
189 private void restoreAuthentication() {
190 if(runAsAuthenticationProvider == null){
191 logger.debug("no RunAsAuthenticationProvider set, thus nothing to restore");
192 }
193 SecurityContext securityContext = SecurityContextHolder.getContext();
194 securityContext.setAuthentication(authentication);
195 logger.debug("last authentication restored: " + (authentication != null ? authentication : "NULL"));
196 }
197
198 /**
199 *
200 * needed to work around the broken @RunAs("ROLE_ADMIN") which seems to be
201 * broken in spring see: https://jira.springsource.org/browse/SEC-1671
202 */
203 private void runAsAuthentication() {
204 if(runAsAuthenticationProvider == null){
205 logger.debug("no RunAsAuthenticationProvider set, skipping run-as authentication");
206 return;
207 }
208
209 SecurityContext securityContext = SecurityContextHolder.getContext();
210 authentication = securityContext.getAuthentication();
211
212
213 Collection<GrantedAuthority> rules = new ArrayList<GrantedAuthority>();
214 rules.add(Role.ROLE_ADMIN);
215 RunAsUserToken adminToken = new RunAsUserToken(
216 RUN_AS_KEY,
217 "system-admin",
218 null,
219 rules,
220 (authentication != null ? authentication.getClass() : AnonymousAuthenticationToken.class));
221
222 Authentication runAsAuthentication = runAsAuthenticationProvider.authenticate(adminToken);
223 SecurityContextHolder.getContext().setAuthentication(runAsAuthentication);
224
225 logger.debug("switched to run-as authentication: " + runAsAuthentication);
226 }
227
228
229 private void checkMetadata() {
230 int metaDataCount = commonService.getCdmMetaData().size();
231 if (metaDataCount == 0){
232 progressMonitor.subTask("Creating Meta Data");
233 createMetadata();
234 }
235 }
236
237 private void checkAdminUser() {
238 User admin = findFirstUser();
239
240 if (admin == null){
241 progressMonitor.subTask("Creating Admin User");
242 admin = createAdminUser();
243 } else {
244 logger.info("Assuming first user '" + admin + "' is admin.");
245 }
246
247 checkAdminRole(admin);
248 progressMonitor.worked(1);
249 }
250
251 private void checkDefaultGroups(){
252
253 progressMonitor.subTask("Checking default groups");
254 checkGroup(Group.groupEditorUuid, "Editor", editorGroupAuthorities);
255 checkGroup(Group.groupProjectManagerUuid, "ProjectManager", projectManagerGroupAuthorities);
256 progressMonitor.worked(1);
257 }
258
259 /**
260 * @param newGroups
261 * @param groupName
262 * @param requiredAuthorities
263 */
264 private void checkGroup(UUID groupUuid, String groupName, String[] requiredAuthorities) {
265 Group group = groupService.load(groupUuid);
266 if(group == null){
267 group = Group.NewInstance();
268 group.setUuid(groupUuid);
269 logger.info("New Group '" + groupName + "' created");
270 }
271 group.setName(groupName); // force name
272
273 Set<GrantedAuthority> grantedAuthorities = group.getGrantedAuthorities();
274
275 for(String a : requiredAuthorities){
276 boolean isMissing = true;
277 for(GrantedAuthority ga : grantedAuthorities){
278 if(a.equals(ga.getAuthority())){
279 isMissing = false;
280 break;
281 }
282 }
283 if(isMissing){
284 GrantedAuthorityImpl newGa = GrantedAuthorityImpl.NewInstance();
285 newGa.setAuthority(a);
286 group.addGrantedAuthority(newGa);
287 logger.info("New GrantedAuthority '" + a + "' added to '" + groupName + "'");
288 }
289 }
290 groupService.saveOrUpdate(group);
291 logger.info("Check of group '" + groupName + "' done");
292 }
293
294 /**
295 * @return
296 */
297 private User findFirstUser() {
298 User firstUser = null;
299 List<User> users = userService.list(null, 1, null, Arrays.asList(new OrderHint[]{new OrderHint("id", OrderHint.SortOrder.ASCENDING)}), null);
300 if(users.size() > 0){
301 firstUser = users.get(0);
302 }
303 return firstUser;
304 }
305
306 private User createAdminUser(){
307
308 User admin = User.NewInstance(Configuration.adminLogin, Configuration.adminPassword);
309 userService.save(admin);
310 logger.info("user '" + Configuration.adminLogin + "' created.");
311 return admin;
312 }
313
314 private void checkAdminRole(User admin) {
315 Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>();
316
317
318 authorities = (Set<GrantedAuthority>) admin.getAuthorities();
319
320 boolean hasRoleAdmin = false;
321 for(GrantedAuthority grau : authorities){
322 if(grau.getAuthority().contentEquals(Role.ROLE_ADMIN.getAuthority())){
323 hasRoleAdmin = true;
324 break;
325 }
326 }
327
328 if(!hasRoleAdmin){
329 authorities.add(getRoleAdmin());
330 admin.setGrantedAuthorities(authorities);
331 progressMonitor.subTask("Creating Admins Role");
332 userService.saveOrUpdate(admin);
333 logger.info("Role " + Role.ROLE_ADMIN.getAuthority() + " for user '" + Configuration.adminLogin + "' created and added");
334 }
335 }
336
337 /**
338 * @return
339 */
340 private GrantedAuthorityImpl getRoleAdmin() {
341 GrantedAuthorityImpl role_admin = grantedAuthorityService.find(Role.ROLE_ADMIN.getUuid());
342 if(role_admin == null){
343 role_admin = Role.ROLE_ADMIN.asNewGrantedAuthority();
344 }
345 return role_admin;
346 }
347
348 private void createMetadata(){
349 List<CdmMetaData> metaData = CdmMetaData.defaultMetaData();
350 commonService.saveAllMetaData(metaData);
351 logger.info("Metadata created.");
352 }
353
354 /**
355 * @return the runAsAuthenticationProvider
356 */
357 public AuthenticationProvider getRunAsAuthenticationProvider() {
358 return runAsAuthenticationProvider;
359 }
360
361 /**
362 * @param runAsAuthenticationProvider the runAsAuthenticationProvider to set
363 */
364 public void setRunAsAuthenticationProvider(AuthenticationProvider runAsAuthenticationProvider) {
365 this.runAsAuthenticationProvider = runAsAuthenticationProvider;
366 }
367
368
369 }