Project

General

Profile

Download (12.6 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.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.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.util.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.util.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
    private void addDeleteCandidates(Set<CdmAuthority> deleteCandidates, DerivedUnit deriveUnit) {
171
        if(deriveUnit == null){
172
            return;
173
        }
174

    
175
        deriveUnit = HibernateProxyHelper.deproxy(deriveUnit);
176
        if(deriveUnit.getCollection() != null){
177
            deleteCandidates.add(new CdmAuthority(deriveUnit.getCollection(), UPDATE_DELETE));
178
        }
179
        for(SpecimenOrObservationBase<?> sob : deriveUnit.getOriginals()){
180
            if(sob == null){
181
                continue;
182
            }
183
            deleteCandidates.add(new CdmAuthority(sob, UPDATE_DELETE));
184
            if(sob instanceof FieldUnit){
185
                addDeleteCandidates(deleteCandidates, (FieldUnit)sob);
186
            } else {
187
                addDeleteCandidates(deleteCandidates, (DerivedUnit)sob);
188
            }
189
        }
190
    }
191

    
192
    private void addDeleteCandidates(Set<CdmAuthority> deleteCandidates, FieldUnit fieldUnit) {
193
        if(fieldUnit == null){
194
            return;
195
        }
196
        fieldUnit = HibernateProxyHelper.deproxy(fieldUnit);
197
        if(fieldUnit.getGatheringEvent() != null){
198
            addDeleteCandidates(deleteCandidates, fieldUnit.getGatheringEvent().getActor());
199
        }
200
    }
201

    
202
    private void addDeleteCandidates(Set<CdmAuthority> deleteCandidates, Reference reference) {
203
        if(reference == null){
204
            return;
205
        }
206
        reference = HibernateProxyHelper.deproxy(reference);
207
        deleteCandidates.add(new CdmAuthority(reference, UPDATE_DELETE));
208
        addDeleteCandidates(deleteCandidates, reference.getAuthorship());
209
        addDeleteCandidates(deleteCandidates, reference.getInReference());
210
    }
211

    
212
    private void addDeleteCandidates(Set<CdmAuthority> deleteCandidates, AgentBase<?> agent) {
213
        if(agent == null){
214
            return;
215
        }
216
        agent = HibernateProxyHelper.deproxy(agent);
217
        deleteCandidates.add(new CdmAuthority(agent, UPDATE_DELETE));
218
        if(agent instanceof TeamOrPersonBase){
219
            if(agent instanceof Team){
220
                List<Person> members = ((Team)agent).getTeamMembers();
221
                if(members != null){
222
                    for(Person p : members){
223
                        if(p != null){
224
                            deleteCandidates.add(new CdmAuthority(p, UPDATE_DELETE));
225
                        }
226
                    }
227
                }
228
            }
229
        }
230
    }
231

    
232
    private void deleteAuthorities(EventSource session, Set<CdmAuthority> deleteCandidates) {
233

    
234
        if(deleteCandidates.isEmpty()){
235
            return;
236
        }
237

    
238
        Collection<String> authorityStrings = new ArrayList<>(deleteCandidates.size());
239
        deleteCandidates.forEach( dc -> authorityStrings.add(dc.toString()));
240

    
241
        // -----------------------------------------------------------------------------------------
242
        // this needs to be executed in a separate session to avoid concurrent modification problems
243
        // See also TaxonGraphHibernateListener for a Listener with also a temporary sub-session
244
        Session newSession = session.getSessionFactory().openSession();
245
        try {
246
            Transaction txState = newSession.beginTransaction();
247

    
248
            Query userQuery = newSession.createQuery("select u from User u join u.grantedAuthorities ga where ga.authority in (:authorities)");
249
            userQuery.setParameterList("authorities", authorityStrings);
250
            @SuppressWarnings("unchecked")
251
            List<User> users = userQuery.list();
252
            for(User user : users){
253
                List<GrantedAuthority> deleteFromUser = user.getGrantedAuthorities().stream().filter(
254
                            ga -> authorityStrings.contains(ga.getAuthority())
255
                        )
256
                        .collect(Collectors.toList());
257
                user.getGrantedAuthorities().removeAll(deleteFromUser);
258
            }
259

    
260
            Query groupQuery = newSession.createQuery("select g from Group g join g.grantedAuthorities ga where ga.authority in (:authorities)");
261
            groupQuery.setParameterList("authorities", authorityStrings);
262
            @SuppressWarnings("unchecked")
263
            List<Group> groups = groupQuery.list();
264
            for(Group group : groups){
265
                List<GrantedAuthority> deleteFromUser = group.getGrantedAuthorities().stream().filter(
266
                            ga -> authorityStrings.contains(ga.getAuthority())
267
                        )
268
                        .collect(Collectors.toList());
269
                group.getGrantedAuthorities().removeAll(deleteFromUser);
270
            }
271
            newSession.flush();
272
            txState.commit();
273
        } finally {
274
            // no catching of the exception, if the session flush fails the transaction should roll back and
275
            // the exception needs to bubble up so that the transaction in enclosing session is also rolled back
276
            newSession.close();
277
        }
278
        // -----------------------------------------------------------------------------------------
279

    
280
        String hql = "delete from GrantedAuthorityImpl as ga where ga.authority in (:authorities)";
281
        Query deleteQuery = session.createQuery(hql);
282
        deleteQuery.setParameterList("authorities", authorityStrings);
283
        deleteQuery.setFlushMode(FlushMode.MANUAL); // workaround for  HHH-11822 (https://hibernate.atlassian.net/browse/HHH-11822)
284
        deleteQuery.executeUpdate();
285

    
286
    }
287

    
288
    @Override
289
    public boolean requiresPostCommitHanding(EntityPersister persister) {
290
        return false;
291
    }
292

    
293
}
    (1-1/1)