Merge branch 'release/5.45.0'
[cdmlib.git] / cdmlib-services / src / main / java / eu / etaxonomy / cdm / api / application / FirstDataInserter.java
1 /**
2 * Copyright (C) 2012 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.application;
10
11 import java.util.Arrays;
12 import java.util.EnumSet;
13 import java.util.List;
14 import java.util.Set;
15 import java.util.UUID;
16
17 import org.apache.logging.log4j.LogManager;
18 import org.apache.logging.log4j.Logger;
19 import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.context.event.ContextRefreshedEvent;
21 import org.springframework.context.event.ContextStartedEvent;
22 import org.springframework.security.core.GrantedAuthority;
23 import org.springframework.transaction.PlatformTransactionManager;
24 import org.springframework.transaction.TransactionDefinition;
25 import org.springframework.transaction.TransactionStatus;
26 import org.springframework.transaction.support.DefaultTransactionDefinition;
27
28 import eu.etaxonomy.cdm.api.service.ICommonService;
29 import eu.etaxonomy.cdm.api.service.IGrantedAuthorityService;
30 import eu.etaxonomy.cdm.api.service.IGroupService;
31 import eu.etaxonomy.cdm.api.service.IUserService;
32 import eu.etaxonomy.cdm.common.monitor.IProgressMonitor;
33 import eu.etaxonomy.cdm.common.monitor.NullProgressMonitor;
34 import eu.etaxonomy.cdm.config.Configuration;
35 import eu.etaxonomy.cdm.model.metadata.CdmMetaData;
36 import eu.etaxonomy.cdm.model.permission.CRUD;
37 import eu.etaxonomy.cdm.model.permission.GrantedAuthorityImpl;
38 import eu.etaxonomy.cdm.model.permission.Group;
39 import eu.etaxonomy.cdm.model.permission.PermissionClass;
40 import eu.etaxonomy.cdm.model.permission.User;
41 import eu.etaxonomy.cdm.persistence.permission.CdmAuthority;
42 import eu.etaxonomy.cdm.persistence.permission.Role;
43 import eu.etaxonomy.cdm.persistence.query.OrderHint;
44
45 /**
46 * The <code>FirstDataInserter</code> is responsible for equipping a new and empty database with
47 * the initial set of data need by the cdmlib. It operates not only on empty databases,
48 * its methods are executed everytime the ApplicationContext has been started up, that is listens
49 * for {@link ContextStartedEvent}s.
50 * <p>
51 * responsibilities:
52 * <ul>
53 * <li>User 'admin' and role 'ROLE_ADMIN'</li>
54 * <li>cdm metadata</li>
55 * <ul>
56 * <p>
57 * The <code>runAsAuthenticationProvider</code> must be set in a security application context, eg:
58 * {@code
59 <bean id="firstDataInserter" class="eu.etaxonomy.cdm.api.application.FirstDataInserter">
60 <property name="runAsAuthenticationProvider" ref="runAsAuthenticationProvider"/>
61 </bean>
62 }
63 *
64 * @author a.kohlbecker
65 * @since Oct 12, 2012
66 */
67 //@RunAs("ROLE_ADMIN") // seems to be broken in spring see: https://jira.springsource.org/browse/SEC-1671
68 public class FirstDataInserter extends AbstractDataInserter {
69
70 private static final Logger logger = LogManager.getLogger();
71
72 private static final EnumSet<CRUD> CREATE_READ = EnumSet.of(CRUD.CREATE, CRUD.READ);
73 private static final EnumSet<CRUD> UPDATE_DELETE = EnumSet.of(CRUD.UPDATE, CRUD.DELETE);
74 private static final EnumSet<CRUD> CREATE_READ_UPDATE = EnumSet.of(CRUD.CREATE, CRUD.READ, CRUD.UPDATE);
75 private static final EnumSet<CRUD> CREATE_READ_UPDATE_DELETE = EnumSet.of(CRUD.CREATE, CRUD.READ, CRUD.UPDATE, CRUD.DELETE);
76
77 public static final GrantedAuthority[] EDITOR_GROUP_AUTHORITIES = new GrantedAuthority[]{
78 new CdmAuthority(PermissionClass.REFERENCE, CREATE_READ),
79 new CdmAuthority(PermissionClass.TAXONNAME, CREATE_READ_UPDATE),
80 new CdmAuthority(PermissionClass.TEAMORPERSONBASE, CREATE_READ),
81 new CdmAuthority(PermissionClass.TAXONBASE, CREATE_READ_UPDATE_DELETE),
82 new CdmAuthority(PermissionClass.DESCRIPTIONBASE, CREATE_READ_UPDATE_DELETE),
83 new CdmAuthority(PermissionClass.DESCRIPTIONELEMENTBASE, CREATE_READ_UPDATE_DELETE),
84 new CdmAuthority(PermissionClass.SPECIMENOROBSERVATIONBASE, CREATE_READ_UPDATE_DELETE),
85 new CdmAuthority(PermissionClass.COLLECTION, CREATE_READ_UPDATE_DELETE),
86 };
87
88 /**
89 * This group will in future replace the group Editor, see issue #7150
90 */
91 public static final CdmAuthority[] EDITOR_GROUP_EXTENDED_CREATE_GROUP_AUTHORITIES = new CdmAuthority[]{
92 new CdmAuthority(PermissionClass.REFERENCE, CREATE_READ),
93 new CdmAuthority(PermissionClass.TAXONNAME, CREATE_READ),
94 new CdmAuthority(PermissionClass.TEAMORPERSONBASE, CREATE_READ),
95 new CdmAuthority(PermissionClass.TAXONBASE, CREATE_READ),
96 new CdmAuthority(PermissionClass.DESCRIPTIONBASE, CREATE_READ),
97 new CdmAuthority(PermissionClass.DESCRIPTIONELEMENTBASE, CREATE_READ),
98 new CdmAuthority(PermissionClass.SPECIMENOROBSERVATIONBASE, CREATE_READ),
99 new CdmAuthority(PermissionClass.COLLECTION, CREATE_READ),
100 };
101
102 public static final GrantedAuthority[] PROJECT_MANAGER_GROUP_AUTHORITIES = new GrantedAuthority[]{
103 new CdmAuthority(PermissionClass.REFERENCE, UPDATE_DELETE),
104 new CdmAuthority(PermissionClass.TAXONNAME, EnumSet.of(CRUD.DELETE)),
105 new CdmAuthority(PermissionClass.TEAMORPERSONBASE, UPDATE_DELETE),
106 Role.ROLE_PROJECT_MANAGER,
107 };
108
109 public static final CdmAuthority[] EDITOR_REFERENCE_GROUP_AUTHORITIES = new CdmAuthority[]{
110 new CdmAuthority(PermissionClass.REFERENCE, UPDATE_DELETE),
111 new CdmAuthority(PermissionClass.TEAMORPERSONBASE, UPDATE_DELETE)
112 };
113
114 public static final Role[] PUBLISH_GROUP_ROLES = new Role[]{
115 Role.ROLE_PUBLISH
116 };
117
118 public static final CdmAuthority[] EDIT_ALL_TAXA_GROUP_AUTHORITIES = new CdmAuthority[]{
119 new CdmAuthority(PermissionClass.TAXONNODE, CREATE_READ_UPDATE_DELETE)
120 };
121
122 public static final Role[] ADMIN_GROUP_ROLES = new Role[]{
123 Role.ROLE_ADMIN
124 };
125
126 public static final Role[] USER_MANAGER_ROLES = new Role[]{
127 Role.ROLE_USER_MANAGER
128 };
129
130 @Autowired
131 private ICommonService commonService;
132
133 @Autowired
134 private IUserService userService;
135
136 @Autowired
137 private IGroupService groupService;
138
139 @Autowired
140 private IGrantedAuthorityService grantedAuthorityService;
141
142 // not to be autowired, since the FirstdataInserter must be usable without security
143 // private AuthenticationProvider runAsAuthenticationProvider = null;
144
145 protected PlatformTransactionManager transactionManager;
146
147 protected DefaultTransactionDefinition txDefinition = new DefaultTransactionDefinition();
148
149 private IProgressMonitor progressMonitor = null;
150
151 private boolean firstDataInserted = false;
152
153 @Autowired
154 public void setTransactionManager(PlatformTransactionManager transactionManager) {
155 this.transactionManager = transactionManager;
156 }
157
158 public FirstDataInserter() {
159 txDefinition.setName("FirstDataInserter.insertFirstData()");
160 txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
161 }
162
163 @Override
164 public void onApplicationEvent(ContextRefreshedEvent event) {
165 if(event.getApplicationContext() instanceof MonitoredGenericApplicationContext){
166 progressMonitor = ((MonitoredGenericApplicationContext)event.getApplicationContext()).getCurrentMonitor();
167 /* TODO set up work amount, currently the amount of work ticks is hard coded
168 * in {@link CdmApplicationControllersetNewDataSource}, but we need some means to register
169 * additional ticks.
170 * see https://dev.e-taxonomy.eu/redmine/issues/3140 (generic way to obtain work ticks of application startup for monitoring)
171 *
172 */
173 } else {
174 progressMonitor = new NullProgressMonitor();
175 }
176
177 insertFirstData();
178 }
179
180 private void insertFirstData() {
181
182 // this ApplicationListener may be called multiple times in nested
183 // application contexts like in web applications
184 if(!firstDataInserted){
185
186 runAsAuthentication(Role.ROLE_ADMIN);
187 TransactionStatus txStatus = transactionManager.getTransaction(txDefinition);
188
189 logger.info("inserting first data");
190 checkAdminUser();
191 checkDefaultGroups();
192 assureRole_REMOTING_forEditors();
193 checkMetadata();
194 firstDataInserted = true;
195
196 transactionManager.commit(txStatus);
197 restoreAuthentication();
198 } else {
199 logger.debug("insertFirstData() already executed before, skipping this time");
200 }
201 }
202
203 private void checkMetadata() {
204 int metaDataCount = commonService.getCdmMetaData().size();
205 if (metaDataCount == 0){
206 progressMonitor.subTask("Creating Meta Data");
207 createMetadata();
208 }
209 }
210
211 private void checkAdminUser() {
212 User admin = findFirstUser();
213
214 if (admin == null){
215 progressMonitor.subTask("Creating Admin User");
216 admin = createAdminUser();
217 } else {
218 logger.info("Assuming first user '" + admin + "' is admin.");
219 }
220
221 checkAdminRole(admin);
222 progressMonitor.worked(1);
223 }
224
225 private void checkDefaultGroups(){
226
227 progressMonitor.subTask("Checking default groups");
228 checkGroup(Group.GROUP_EDITOR_UUID, Group.GROUP_EDITOR_NAME, EDITOR_GROUP_AUTHORITIES);
229 checkGroup(Group.GROUP_EDITOR_EXTENDED_CREATE_UUID, Group.GROUP_EDITOR_EXTENDED_CREATE_NAME, EDITOR_GROUP_EXTENDED_CREATE_GROUP_AUTHORITIES);
230 checkGroup(Group.GROUP_PROJECT_MANAGER_UUID, Group.GROUP_PROJECT_MANAGER_NAME, PROJECT_MANAGER_GROUP_AUTHORITIES);
231 checkGroup(Group.GROUP_ADMIN_UUID, Group.GROUP_ADMIN_NAME, ADMIN_GROUP_ROLES);
232 checkGroup(Group.GROUP_EDITOR_REFERENCE_UUID, Group.GROUP_EDITOR_REFERENCE_NAME, EDITOR_REFERENCE_GROUP_AUTHORITIES);
233 checkGroup(Group.GROUP_ALLOW_ALL_TAXA_UUID, Group.GROUP_ALLOW_ALL_TAXA_NAME, EDIT_ALL_TAXA_GROUP_AUTHORITIES);
234 checkGroup(Group.GROUP_PUBLISH_UUID, Group.GROUP_PUBLISH_NAME, PUBLISH_GROUP_ROLES);
235 checkGroup(Group.GROUP_USER_MANAGER_UUID, Group.GROUP_USER_MANAGER_NAME, USER_MANAGER_ROLES);
236 progressMonitor.worked(1);
237 }
238
239 private void checkGroup(UUID groupUuid, String groupName, GrantedAuthority[] requiredAuthorities) {
240 Group group = groupService.load(groupUuid);
241 if(group == null){
242 group = Group.NewInstance();
243 group.setUuid(groupUuid);
244 logger.info("New Group '" + groupName + "' created");
245 }
246 group.setName(groupName); // force default name
247
248 Set<GrantedAuthority> grantedAuthorities = group.getGrantedAuthorities();
249
250 for(GrantedAuthority requiredAuthority : requiredAuthorities){
251 boolean isMissing = true;
252 for(GrantedAuthority ga : grantedAuthorities){
253 if(requiredAuthority.getAuthority().equals(ga.getAuthority())){
254 isMissing = false;
255 break;
256 }
257 }
258 if(isMissing){
259 addMissingAuthority(groupName, group, requiredAuthority);
260 }
261 }
262
263 groupService.saveOrUpdate(group);
264 logger.info("Check of group '" + groupName + "' done");
265 }
266
267 private void addMissingAuthority(String groupName, Group group, GrantedAuthority requiredAuthority) {
268 //NOTE: we still have to do this by string until UUIDs are fixed
269 GrantedAuthorityImpl newGa = grantedAuthorityService.findAuthorityString(requiredAuthority.getAuthority());
270
271 if (newGa == null){
272 newGa = GrantedAuthorityImpl.NewInstance(requiredAuthority.toString());
273 if (requiredAuthority instanceof Role){
274 newGa.setUuid(((Role)requiredAuthority).getUuid());
275 }
276 }
277
278 group.addGrantedAuthority(newGa);
279 logger.info("New GrantedAuthority '" + requiredAuthority + "' added to '" + groupName + "'");
280 }
281
282 private User findFirstUser() {
283 User firstUser = null;
284 List<User> users = userService.list(null, 1, null, Arrays.asList(new OrderHint[]{new OrderHint("id", OrderHint.SortOrder.ASCENDING)}), null);
285 if(users.size() > 0){
286 firstUser = users.get(0);
287 }
288 return firstUser;
289 }
290
291 private User createAdminUser(){
292
293 User admin = User.NewInstance(Configuration.adminLogin, Configuration.adminPassword);
294 userService.save(admin);
295 logger.info("user '" + Configuration.adminLogin + "' created.");
296 return admin;
297 }
298
299 /**
300 * Assures the {@link Role#ROLE_REMOTING} exists.
301 * <p>
302 * If the role is missing in the db it will be created and added to the Groups <code>Editor</code> and <code>EditorExtendedCreate</code>.
303 * <p>
304 * The role will however not be added to the editor groups in case the role exist but is missing from one of these groups. This allows removal
305 * of the role from the editor groups to withdraw the remote editing permission from editors in general for a project.
306 * <p>
307 * see https://dev.e-taxonomy.eu/redmine/issues/7972
308 */
309 private void assureRole_REMOTING_forEditors(){
310
311 if(!roleExists(Role.ROLE_REMOTING)){
312 GrantedAuthorityImpl roleRemoting = assureRole(Role.ROLE_REMOTING);
313 Group groupEditor = groupService.load(Group.GROUP_EDITOR_UUID);
314 groupEditor.addGrantedAuthority(roleRemoting);
315 groupService.saveOrUpdate(groupEditor);
316 Group groupEditorExtendedCreate = groupService.load(Group.GROUP_EDITOR_EXTENDED_CREATE_UUID);
317 groupEditorExtendedCreate.addGrantedAuthority(roleRemoting);
318 groupService.saveOrUpdate(groupEditorExtendedCreate);
319 }
320 }
321
322 private void checkAdminRole(User admin) {
323
324 Set<GrantedAuthority> authorities = (Set<GrantedAuthority>) admin.getAuthorities();
325
326 boolean hasRoleAdmin = false;
327 for(GrantedAuthority grau : authorities){
328 if(grau.getAuthority().contentEquals(Role.ROLE_ADMIN.getAuthority())){
329 hasRoleAdmin = true;
330 break;
331 }
332 }
333
334 if(!hasRoleAdmin){
335 authorities.add(assureRole(Role.ROLE_ADMIN));
336 admin.setGrantedAuthorities(authorities);
337 progressMonitor.subTask("Creating Admins Role");
338 userService.saveOrUpdate(admin);
339 logger.info("Role " + Role.ROLE_ADMIN.getAuthority() + " for user '" + Configuration.adminLogin + "' created and added");
340 }
341 }
342
343 private GrantedAuthorityImpl assureRole(Role role) {
344 GrantedAuthorityImpl roleLoaded = grantedAuthorityService.find(role.getUuid());
345 if(roleLoaded == null){
346 roleLoaded = grantedAuthorityService.save(role.asNewGrantedAuthority());
347 }
348 return roleLoaded;
349 }
350
351 private boolean roleExists(Role role) {
352 GrantedAuthorityImpl roleLoaded = grantedAuthorityService.find(role.getUuid());
353 return roleLoaded != null;
354 }
355
356 private void createMetadata(){
357 List<CdmMetaData> metaData = CdmMetaData.defaultMetaData();
358 commonService.saveAllMetaData(metaData);
359 logger.info("Metadata created.");
360 }
361 }