Project

General

Profile

Download (11 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
 * Copyright (C) 2011 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.beans.Introspector;
12
import java.io.Serializable;
13
import java.util.ArrayList;
14
import java.util.Collection;
15
import java.util.EnumSet;
16
import java.util.HashMap;
17
import java.util.HashSet;
18
import java.util.List;
19
import java.util.Map;
20
import java.util.Set;
21

    
22
import org.apache.commons.lang.ArrayUtils;
23
import org.apache.log4j.Logger;
24
import org.hibernate.EmptyInterceptor;
25
import org.hibernate.type.Type;
26
import org.springframework.security.core.context.SecurityContextHolder;
27
import org.springframework.stereotype.Component;
28

    
29
import eu.etaxonomy.cdm.database.PermissionDeniedException;
30
import eu.etaxonomy.cdm.model.CdmBaseType;
31
import eu.etaxonomy.cdm.model.common.CdmBase;
32
import eu.etaxonomy.cdm.model.common.IPublishable;
33
import eu.etaxonomy.cdm.persistence.hibernate.permission.CRUD;
34
import eu.etaxonomy.cdm.persistence.hibernate.permission.ICdmPermissionEvaluator;
35
import eu.etaxonomy.cdm.persistence.hibernate.permission.Operation;
36
import eu.etaxonomy.cdm.persistence.hibernate.permission.Role;
37

    
38
/**
39
 * @author k.luther
40
 * @author a.kohlbecker
41
 *
42
 */
43
@Component
44
public class CdmSecurityHibernateInterceptor extends EmptyInterceptor {
45

    
46
    private static final long serialVersionUID = 8477758472369568074L;
47

    
48
    public static final Logger logger = Logger.getLogger(CdmSecurityHibernateInterceptor.class);
49

    
50

    
51
    private ICdmPermissionEvaluator permissionEvaluator;
52

    
53
    public ICdmPermissionEvaluator getPermissionEvaluator() {
54
        return permissionEvaluator;
55
    }
56

    
57
    public void setPermissionEvaluator(ICdmPermissionEvaluator permissionEvaluator) {
58
        this.permissionEvaluator = permissionEvaluator;
59
    }
60

    
61
    /**
62
     * The exculdeMap must map every property to the CdmBase type !!!
63
     */
64
    public static final Map<Class<? extends CdmBase>, Set<String>> exculdeMap = new HashMap<Class<? extends CdmBase>, Set<String>>();
65

    
66
    static{
67
//        disabled since no longer needed, see https://dev.e-taxonomy.eu/trac/ticket/4111#comment:8
68
//        exculdeMap.put(TaxonName.class, new HashSet<String>());
69

    
70
        Set<String> defaultExculdes = new HashSet<String>();
71
        defaultExculdes.add("createdBy");  //created by is changed by CdmPreDataChangeListener after save. This is handled as a change and therefore throws a security exception during first insert if only CREATE rights exist
72
        defaultExculdes.add("created");  // same behavior was not yet observed for "created", but to be on the save side we also exclude "created"
73
        defaultExculdes.add("updatedBy");
74
        defaultExculdes.add("updated");
75

    
76
        for ( CdmBaseType type: CdmBaseType.values()){
77
            exculdeMap.put(type.getBaseClass(), new HashSet<String>());
78
            exculdeMap.get(type.getBaseClass()).addAll(defaultExculdes);
79
        }
80
        exculdeMap.put(CdmBase.class, new HashSet<String>());
81
        exculdeMap.get(CdmBase.class).addAll(defaultExculdes);
82

    
83

    
84
        /*
85
         * default fields required for each type for which excludes are defined
86
         */
87
//        exculdeMap.get(TaxonName.class).add("updatedBy");
88
//        exculdeMap.get(TaxonName.class).add("created");
89
//        exculdeMap.get(TaxonName.class).add("updated");
90

    
91
        /*
92
         * the specific excludes
93
         */
94
//        exculdeMap.get(TaxonName.class).add("taxonBases");
95
    }
96

    
97
    @Override
98
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] type) {
99

    
100
        if (SecurityContextHolder.getContext().getAuthentication() == null || !(entity instanceof CdmBase)) {
101
            return true;
102
        }
103
        // evaluate throws EvaluationFailedException
104
        checkPermissions((CdmBase) entity, Operation.CREATE);
105
        logger.debug("permission check suceeded - object creation granted");
106
        return true;
107
    }
108

    
109
    @Override
110
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
111

    
112
        if (SecurityContextHolder.getContext().getAuthentication() == null || !(entity instanceof CdmBase)) {
113
            return true;
114
        }
115
        CdmBase cdmEntity = (CdmBase) entity;
116
        if (previousState == null){
117
            return onSave(cdmEntity, id, currentState, propertyNames, null);
118
        }
119
        Set<String> excludes = exculdeMap.get(baseType(cdmEntity));
120
        excludes.addAll(unprotectedCacheFields(currentState, previousState, propertyNames));
121
        if (isModified(currentState, previousState, propertyNames, excludes)) {
122
            // evaluate throws EvaluationFailedException
123
            //if(cdmEntity.getCreated())
124
            checkPermissions(cdmEntity, Operation.UPDATE);
125
            logger.debug("Operation.UPDATE permission check suceeded - object update granted");
126

    
127
            if(IPublishable.class.isAssignableFrom(entity.getClass())){
128
                if(namedPropertyIsModified(currentState, previousState, propertyNames, "publish")){
129
                    checkRoles(Role.ROLE_PUBLISH, Role.ROLE_ADMIN);
130
                    logger.debug("Role.ROLE_PUBLISH permission check suceeded - object update granted");
131
                }
132
            }
133
        }
134
        return true;
135
    }
136

    
137
    /**
138
     * Detects all cache fields and the according protection flags. For cache fields which are not
139
     * protected the name of the cache field and of the protection flag are returned.
140
     * <p>
141
     * This method relies on  the convention that the protection flag for cache fields are named like
142
     * {@code protected{CacheFieldName} } whereas the cache fields a always ending with "Cache"
143
     *
144
     * @param currentState
145
     * @param previousState
146
     * @param propertyNames
147
     * @return
148
     */
149
    protected Collection<? extends String> unprotectedCacheFields(Object[] currentState, Object[] previousState,
150
            String[] propertyNames) {
151

    
152
        List<String> excludes = new ArrayList<>();
153
        for(int i = 0; i < propertyNames.length; i ++){
154
            if(propertyNames[i].matches("^protected.*Cache$")){
155
                if(currentState[i] instanceof Boolean && ((Boolean)currentState[i]) == false && currentState[i].equals(previousState[i])){
156
                    excludes.add(propertyNames[i]);
157
                    String cacheFieldName = propertyNames[i].replace("protected", "");
158
                    cacheFieldName = Introspector.decapitalize(cacheFieldName);
159
                    excludes.add(cacheFieldName);
160
                }
161
            }
162
        }
163

    
164
        return excludes;
165
    }
166

    
167
    private Class<? extends CdmBase> baseType(CdmBase cdmEntity) {
168
        Class<? extends CdmBase> basetype = CdmBaseType.baseTypeFor(cdmEntity.getClass());
169
        return basetype == null ? CdmBase.class : basetype;
170
    }
171

    
172

    
173
    @Override
174
    public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
175

    
176
        if (SecurityContextHolder.getContext().getAuthentication() == null || !(entity instanceof CdmBase)) {
177
            return;
178
        }
179
        CdmBase cdmEntity = (CdmBase) entity;
180
        // evaluate throws EvaluationFailedException
181
        checkPermissions(cdmEntity, Operation.DELETE);
182
        logger.debug("permission check suceeded - object update granted");
183
        return;
184
    }
185

    
186
    /**
187
     * checks if the current authentication has the <code>expectedPermission</code> on the supplied <code>entity</code>.
188
     * Throws an {@link PermissionDeniedException} if the evaluation fails.
189
     *
190
     * @param entity
191
     * @param expectedOperation
192
     */
193
    private void checkPermissions(CdmBase entity, EnumSet<CRUD> expectedOperation) {
194

    
195
        if (!permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), entity, expectedOperation)){
196
            throw new PermissionDeniedException(SecurityContextHolder.getContext().getAuthentication(), entity, expectedOperation);
197
        }
198
    }
199

    
200
    /**
201
     * checks if the current authentication has at least one of the <code>roles</code>.
202
     * Throws an {@link PermissionDeniedException} if the evaluation fails.
203
     * @param roles
204
     */
205
    private void checkRoles(Role ... roles) {
206

    
207
        if (!permissionEvaluator.hasOneOfRoles(SecurityContextHolder.getContext().getAuthentication(), roles)){
208
            throw new PermissionDeniedException(SecurityContextHolder.getContext().getAuthentication(), roles);
209
        }
210
    }
211

    
212
    /**
213
     * Checks if the CDM entity as been modified by comparing the current with the previous state.
214
     *
215
     * @param currentState
216
     * @param previousState
217
     * @return true if the currentState and previousState differ.
218
     */
219
    private boolean isModified(Object[] currentState, Object[] previousState, String[] propertyNames, Set<String> excludes) {
220

    
221
        Set<Integer> excludeIds = null;
222

    
223
        if(excludes != null && excludes.size() > 0) {
224
            excludeIds = new HashSet<Integer>(excludes.size());
225
            int i = 0;
226
            for(String prop : propertyNames){
227
                if(excludes.contains(prop)){
228
                    excludeIds.add(i);
229
                }
230
                if(excludeIds.size() == excludes.size()){
231
                    // all ids found
232
                    break;
233
                }
234
                i++;
235
            }
236
        }
237

    
238
        for (int i = 0; i<currentState.length; i++){
239
            if((excludeIds == null || !excludeIds.contains(i))){
240
                if(propertyIsModified(currentState, previousState, i)){
241
                    if(logger.isDebugEnabled()){
242
                        logger.debug("modified property found: " + propertyNames[i] + ", previousState: " + previousState[i] + ", currentState: " + currentState[i] );
243
                    }
244
                    return true;
245
                }
246
            }
247
        }
248

    
249
        return false;
250
    }
251

    
252
    /**
253
     * Compares the object states at the property denoted by the key parameter and returns true if they differ in this property
254
     *
255
     * @param currentState
256
     * @param previousState
257
     * @param isModified
258
     * @param key
259
     * @return
260
     */
261
    private boolean propertyIsModified(Object[] currentState, Object[] previousState, int key) {
262
        if (currentState[key]== null ) {
263
            if ( previousState[key]!= null) {
264
                return true;
265
            }
266
        }
267
        if (currentState[key]!= null ){
268
            if (previousState[key] == null){
269
                return true;
270
            }
271
        }
272
        if (currentState[key]!= null && previousState[key] != null){
273
            if (!currentState[key].equals(previousState[key])) {
274
                return true;
275
            }
276
        }
277
        return false;
278
    }
279

    
280
    private boolean namedPropertyIsModified(Object[] currentState, Object[] previousState, String[] propertyNames, String propertyNameToTest) {
281

    
282
        int key = ArrayUtils.indexOf(propertyNames, propertyNameToTest);
283
        return propertyIsModified(currentState, previousState, key);
284
    }
285

    
286
}
(10-10/21)