85a08d158b36d66ddcfc474001f71094d7bd7970
[cdm-vaadin.git] / src / main / java / eu / etaxonomy / cdm / persistence / hibernate / GrantedAuthorityRevokingRegistrationUpdateLister.java
1 /**
2 * Copyright (C) 2017 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.persistence.hibernate;
10
11 import java.util.ArrayList;
12 import java.util.Collection;
13 import java.util.EnumSet;
14 import java.util.HashSet;
15 import java.util.List;
16 import java.util.Set;
17 import java.util.stream.Collectors;
18
19 import org.hibernate.FlushMode;
20 import org.hibernate.Query;
21 import org.hibernate.Session;
22 import org.hibernate.Transaction;
23 import org.hibernate.event.spi.EventSource;
24 import org.hibernate.event.spi.PostUpdateEvent;
25 import org.hibernate.event.spi.PostUpdateEventListener;
26 import org.hibernate.persister.entity.EntityPersister;
27 import org.springframework.security.core.GrantedAuthority;
28
29 import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
30 import eu.etaxonomy.cdm.model.agent.AgentBase;
31 import eu.etaxonomy.cdm.model.agent.Person;
32 import eu.etaxonomy.cdm.model.agent.Team;
33 import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;
34 import eu.etaxonomy.cdm.model.name.Registration;
35 import eu.etaxonomy.cdm.model.name.RegistrationStatus;
36 import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
37 import eu.etaxonomy.cdm.model.name.TaxonName;
38 import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
39 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
40 import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
41 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
42 import eu.etaxonomy.cdm.model.permission.CRUD;
43 import eu.etaxonomy.cdm.model.permission.Group;
44 import eu.etaxonomy.cdm.model.permission.User;
45 import eu.etaxonomy.cdm.model.reference.Reference;
46 import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmAuthority;
47
48 /**
49 * This Hibernate {@link PostUpdateEventListener} is responsible for
50 * revoking GrantedAuthorities from any user which is having per entity
51 * permissions in the object graph of the `Registration`being updated
52 * This encompasses GrantedAuthotities with the CRUD values CRUD.UPDATE, CRUD.DELETE.
53 * Please refer to the method documentation of {@link #collectDeleteCandidates(Registration)}
54 * for further details.
55 * <p>
56 * The according permissions are revoked when the RegistrationStatus is being changed
57 * by a database update. The RegistrationStatus causing this are contained in the constant
58 * {@link GrantedAuthorityRevokingRegistrationUpdateLister#MODIFICATION_STOP_STATES MODIFICATION_STOP_STATES}
59 *
60 *
61 * @author a.kohlbecker
62 * @since Dec 18, 2017
63 *
64 */
65 public class GrantedAuthorityRevokingRegistrationUpdateLister implements PostUpdateEventListener {
66
67 private static final long serialVersionUID = -3542204523291766866L;
68
69 /**
70 * Registrations having these states must no longer be modifiable by users having
71 * only per entity permissions on the Registration subgraph.
72 */
73 private static final EnumSet<RegistrationStatus> MODIFICATION_STOP_STATES = EnumSet.of(
74 RegistrationStatus.PUBLISHED,
75 RegistrationStatus.READY,
76 RegistrationStatus.REJECTED
77 );
78
79 private static final EnumSet<CRUD> UPDATE_DELETE = EnumSet.of(CRUD.UPDATE, CRUD.DELETE);
80
81 private static final EnumSet<CRUD> UPDATE = EnumSet.of(CRUD.UPDATE);
82
83 /**
84 * {@inheritDoc}
85 */
86 @Override
87 public void onPostUpdate(PostUpdateEvent event) {
88 if( event.getEntity() instanceof Registration){
89 Registration reg = (Registration)event.getEntity();
90 if(reg.getStatus() != null && MODIFICATION_STOP_STATES.contains(reg.getStatus())){
91 Set<CdmAuthority> deleteCandidates = collectDeleteCandidates(reg);
92 deleteAuthorities(event.getSession(), deleteCandidates);
93 }
94 }
95 }
96
97 /**
98 * Walks the entity graph of the Registration instance and collects all authorities which
99 * could have been granted to users. Code parts in which this could have happened can be
100 * found by searching for usage of the methods {@link eu.etaxonomy.cdm.api.utility.UserHelper#createAuthorityForCurrentUser(eu.etaxonomy.cdm.model.common.CdmBase, EnumSet, String)
101 * UserHelper.createAuthorityForCurrentUser(eu.etaxonomy.cdm.model.common.CdmBase, EnumSet, String)} and
102 * {@link eu.etaxonomy.cdm.api.utility.UserHelper#createAuthorityForCurrentUser(Class, Integer, EnumSet, String)
103 * UserHelper.createAuthorityForCurrentUser(Class, Integer, EnumSet, String)}
104 * <p>
105 * At the time of implementing this function these places are:
106 * <ul>
107 * <li><code>RegistrationEditorPresenter.guaranteePerEntityCRUDPermissions(...)</code></li>
108 * <li><code>RegistrationWorkingsetPresenter.createNewRegistrationForName(Integer taxonNameId)</code></li>
109 * <li><code>TaxonNameEditorPresenter.guaranteePerEntityCRUDPermissions(...)</code></li>
110 * <li><code>ReferenceEditorPresenter.guaranteePerEntityCRUDPermissions(...)</code></li>
111 * <li><code>PersonField.commit()</code></li>
112 * <li><code>TeamOrPersonField.commit()</code></li>
113 * <li><code>SpecimenTypeDesignationWorkingsetEditorPresenter.saveBean(SpecimenTypeDesignationWorkingSetDTO dto)</code></li>
114 * </ul>
115 *
116 * @param reg the Registration
117 * @return the set of all possible CdmAuthorities that could have been granted to
118 * individual users.
119 */
120 private Set<CdmAuthority> collectDeleteCandidates(Registration reg){
121 Set<CdmAuthority> deleteCandidates = new HashSet<CdmAuthority>();
122 // add authority for Registration
123 deleteCandidates.add(new CdmAuthority(reg, RegistrationStatus.PREPARATION.name(), UPDATE));
124 if(reg.getName() != null){
125 addDeleteCandidates(deleteCandidates, reg.getName());
126 }
127 for(TypeDesignationBase td : reg.getTypeDesignations()){
128 addDeleteCandidates(deleteCandidates, td);
129 }
130
131 return deleteCandidates;
132
133 }
134
135 /**
136 * @param deleteCandidates
137 * @param name
138 */
139 private void addDeleteCandidates(Set<CdmAuthority> deleteCandidates, TaxonName name) {
140 if(name == null){
141 return;
142 }
143 name = HibernateProxyHelper.deproxy(name);
144 deleteCandidates.add(new CdmAuthority(name, UPDATE_DELETE));
145 addDeleteCandidates(deleteCandidates, name.getNomenclaturalReference());
146 addDeleteCandidates(deleteCandidates, name.getCombinationAuthorship());
147 addDeleteCandidates(deleteCandidates, name.getExCombinationAuthorship());
148 addDeleteCandidates(deleteCandidates, name.getBasionymAuthorship());
149 addDeleteCandidates(deleteCandidates, name.getExBasionymAuthorship());
150 }
151
152
153 /**
154 * @param deleteCandidates
155 * @param td
156 */
157 private void addDeleteCandidates(Set<CdmAuthority> deleteCandidates, TypeDesignationBase td) {
158 if(td == null){
159 return;
160 }
161 td = HibernateProxyHelper.deproxy(td);
162 deleteCandidates.add(new CdmAuthority(td, UPDATE_DELETE));
163 addDeleteCandidates(deleteCandidates, td.getCitation());
164 if(td instanceof SpecimenTypeDesignation){
165 SpecimenTypeDesignation std = (SpecimenTypeDesignation)td;
166 addDeleteCandidates(deleteCandidates, std.getTypeSpecimen());
167 }
168 }
169
170 /**
171 * @param deleteCandidates
172 * @param typeSpecimen
173 */
174 private void addDeleteCandidates(Set<CdmAuthority> deleteCandidates, DerivedUnit deriveUnit) {
175 if(deriveUnit == null){
176 return;
177 }
178
179 deriveUnit = HibernateProxyHelper.deproxy(deriveUnit);
180 if(deriveUnit.getCollection() != null){
181 deleteCandidates.add(new CdmAuthority(deriveUnit.getCollection(), UPDATE_DELETE));
182 }
183 for(SpecimenOrObservationBase sob : deriveUnit.getOriginals()){
184 if(sob == null){
185 continue;
186 }
187 deleteCandidates.add(new CdmAuthority(sob, UPDATE_DELETE));
188 if(sob instanceof FieldUnit){
189 addDeleteCandidates(deleteCandidates, (FieldUnit)sob);
190 } else {
191 addDeleteCandidates(deleteCandidates, (DerivedUnit)sob);
192 }
193 }
194 }
195
196 private void addDeleteCandidates(Set<CdmAuthority> deleteCandidates, FieldUnit fieldUnit) {
197 if(fieldUnit == null){
198 return;
199 }
200 fieldUnit = HibernateProxyHelper.deproxy(fieldUnit);
201 if(fieldUnit.getGatheringEvent() != null){
202 addDeleteCandidates(deleteCandidates, fieldUnit.getGatheringEvent().getActor());
203 }
204 }
205
206 /**
207 * @param deleteCandidates
208 * @param nomenclaturalReference
209 */
210 private void addDeleteCandidates(Set<CdmAuthority> deleteCandidates, Reference reference) {
211 if(reference == null){
212 return;
213 }
214 reference = HibernateProxyHelper.deproxy(reference);
215 deleteCandidates.add(new CdmAuthority(reference, UPDATE_DELETE));
216 addDeleteCandidates(deleteCandidates, reference.getAuthorship());
217 addDeleteCandidates(deleteCandidates, reference.getInReference());
218 }
219
220 private void addDeleteCandidates(Set<CdmAuthority> deleteCandidates, AgentBase<?> agent) {
221 if(agent == null){
222 return;
223 }
224 agent = HibernateProxyHelper.deproxy(agent);
225 deleteCandidates.add(new CdmAuthority(agent, UPDATE_DELETE));
226 if(agent instanceof TeamOrPersonBase){
227 if(agent instanceof Team){
228 List<Person> members = ((Team)agent).getTeamMembers();
229 if(members != null){
230 for(Person p : members){
231 if(p != null){
232 deleteCandidates.add(new CdmAuthority(p, UPDATE_DELETE));
233 }
234 }
235 }
236 }
237 }
238
239 }
240
241
242
243
244 /**
245 * @param deleteCandidates
246 */
247 private void deleteAuthorities(EventSource session, Set<CdmAuthority> deleteCandidates) {
248
249 if(deleteCandidates.isEmpty()){
250 return;
251 }
252
253 Collection<String> authorityStrings = new ArrayList<String>(deleteCandidates.size());
254 deleteCandidates.forEach( dc -> authorityStrings.add(dc.toString()));
255
256 // -----------------------------------------------------------------------------------------
257 // this needs to be executed in a separate session to avoid concurrent modification problems
258 // See also TaxonGraphHibernateListener for a Listener with also a temporary sub-session
259 Session newSession = session.getSessionFactory().openSession();
260 try {
261 Transaction txState = newSession.beginTransaction();
262
263 Query userQuery = newSession.createQuery("select u from User u join u.grantedAuthorities ga where ga.authority in (:authorities)");
264 userQuery.setParameterList("authorities", authorityStrings);
265 List<User> users = userQuery.list();
266 for(User user : users){
267 List<GrantedAuthority> deleteFromUser = user.getGrantedAuthorities().stream().filter(
268 ga -> authorityStrings.contains(ga.getAuthority())
269 )
270 .collect(Collectors.toList());
271 user.getGrantedAuthorities().removeAll(deleteFromUser);
272 }
273
274 Query groupQuery = newSession.createQuery("select g from Group g join g.grantedAuthorities ga where ga.authority in (:authorities)");
275 groupQuery.setParameterList("authorities", authorityStrings);
276 List<Group> groups = groupQuery.list();
277 for(Group group : groups){
278 List<GrantedAuthority> deleteFromUser = group.getGrantedAuthorities().stream().filter(
279 ga -> authorityStrings.contains(ga.getAuthority())
280 )
281 .collect(Collectors.toList());
282 group.getGrantedAuthorities().removeAll(deleteFromUser);
283 }
284 newSession.flush();
285 txState.commit();
286 } finally {
287 // no catching of the exception, if the session flush fails the transaction should roll back and
288 // the exception needs to bubble up so that the transaction in enclosing session is also rolled back
289 newSession.close();
290 }
291 // -----------------------------------------------------------------------------------------
292
293 String hql = "delete from GrantedAuthorityImpl as ga where ga.authority in (:authorities)";
294 Query deleteQuery = session.createQuery(hql);
295 deleteQuery.setParameterList("authorities", authorityStrings);
296 deleteQuery.setFlushMode(FlushMode.MANUAL); // workaround for HHH-11822 (https://hibernate.atlassian.net/browse/HHH-11822)
297 deleteQuery.executeUpdate();
298
299 }
300
301 @Override
302 public boolean requiresPostCommitHanding(EntityPersister persister) {
303 return false;
304 }
305
306 }