2 * Copyright (C) 2017 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
.persistence
.hibernate
;
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
;
17 import java
.util
.stream
.Collectors
;
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
;
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
;
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.
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}
61 * @author a.kohlbecker
65 public class GrantedAuthorityRevokingRegistrationUpdateLister
implements PostUpdateEventListener
{
67 private static final long serialVersionUID
= -3542204523291766866L;
70 * Registrations having these states must no longer be modifiable by users having
71 * only per entity permissions on the Registration subgraph.
73 private static final EnumSet
<RegistrationStatus
> MODIFICATION_STOP_STATES
= EnumSet
.of(
74 RegistrationStatus
.PUBLISHED
,
75 RegistrationStatus
.READY
,
76 RegistrationStatus
.REJECTED
79 private static final EnumSet
<CRUD
> UPDATE_DELETE
= EnumSet
.of(CRUD
.UPDATE
, CRUD
.DELETE
);
81 private static final EnumSet
<CRUD
> UPDATE
= EnumSet
.of(CRUD
.UPDATE
);
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
);
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)}
105 * At the time of implementing this function these places are:
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>
116 * @param reg the Registration
117 * @return the set of all possible CdmAuthorities that could have been granted to
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());
127 for(TypeDesignationBase td
: reg
.getTypeDesignations()){
128 addDeleteCandidates(deleteCandidates
, td
);
131 return deleteCandidates
;
136 * @param deleteCandidates
139 private void addDeleteCandidates(Set
<CdmAuthority
> deleteCandidates
, TaxonName name
) {
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());
154 * @param deleteCandidates
157 private void addDeleteCandidates(Set
<CdmAuthority
> deleteCandidates
, TypeDesignationBase td
) {
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());
171 * @param deleteCandidates
172 * @param typeSpecimen
174 private void addDeleteCandidates(Set
<CdmAuthority
> deleteCandidates
, DerivedUnit deriveUnit
) {
175 if(deriveUnit
== null){
179 deriveUnit
= HibernateProxyHelper
.deproxy(deriveUnit
);
180 if(deriveUnit
.getCollection() != null){
181 deleteCandidates
.add(new CdmAuthority(deriveUnit
.getCollection(), UPDATE_DELETE
));
183 for(SpecimenOrObservationBase sob
: deriveUnit
.getOriginals()){
187 deleteCandidates
.add(new CdmAuthority(sob
, UPDATE_DELETE
));
188 if(sob
instanceof FieldUnit
){
189 addDeleteCandidates(deleteCandidates
, (FieldUnit
)sob
);
191 addDeleteCandidates(deleteCandidates
, (DerivedUnit
)sob
);
196 private void addDeleteCandidates(Set
<CdmAuthority
> deleteCandidates
, FieldUnit fieldUnit
) {
197 if(fieldUnit
== null){
200 fieldUnit
= HibernateProxyHelper
.deproxy(fieldUnit
);
201 if(fieldUnit
.getGatheringEvent() != null){
202 addDeleteCandidates(deleteCandidates
, fieldUnit
.getGatheringEvent().getActor());
207 * @param deleteCandidates
208 * @param nomenclaturalReference
210 private void addDeleteCandidates(Set
<CdmAuthority
> deleteCandidates
, Reference reference
) {
211 if(reference
== null){
214 reference
= HibernateProxyHelper
.deproxy(reference
);
215 deleteCandidates
.add(new CdmAuthority(reference
, UPDATE_DELETE
));
216 addDeleteCandidates(deleteCandidates
, reference
.getAuthorship());
217 addDeleteCandidates(deleteCandidates
, reference
.getInReference());
220 private void addDeleteCandidates(Set
<CdmAuthority
> deleteCandidates
, AgentBase
<?
> agent
) {
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();
230 for(Person p
: members
){
232 deleteCandidates
.add(new CdmAuthority(p
, UPDATE_DELETE
));
245 * @param deleteCandidates
247 private void deleteAuthorities(EventSource session
, Set
<CdmAuthority
> deleteCandidates
) {
249 if(deleteCandidates
.isEmpty()){
253 Collection
<String
> authorityStrings
= new ArrayList
<String
>(deleteCandidates
.size());
254 deleteCandidates
.forEach( dc
-> authorityStrings
.add(dc
.toString()));
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();
261 Transaction txState
= newSession
.beginTransaction();
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())
270 .collect(Collectors
.toList());
271 user
.getGrantedAuthorities().removeAll(deleteFromUser
);
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())
281 .collect(Collectors
.toList());
282 group
.getGrantedAuthorities().removeAll(deleteFromUser
);
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
291 // -----------------------------------------------------------------------------------------
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();
302 public boolean requiresPostCommitHanding(EntityPersister persister
) {