Project

General

Profile

Download (12.7 KB) Statistics
| Branch: | Tag: | Revision:
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.common.Group;
35
import eu.etaxonomy.cdm.model.common.User;
36
import eu.etaxonomy.cdm.model.name.Registration;
37
import eu.etaxonomy.cdm.model.name.RegistrationStatus;
38
import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
39
import eu.etaxonomy.cdm.model.name.TaxonName;
40
import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
41
import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
42
import eu.etaxonomy.cdm.model.occurrence.FieldUnit;
43
import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
44
import eu.etaxonomy.cdm.model.reference.Reference;
45
import eu.etaxonomy.cdm.persistence.hibernate.permission.CRUD;
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
}
    (1-1/1)