Project

General

Profile

Download (9.94 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

    
12

    
13
import java.io.Serializable;
14
import java.util.EnumSet;
15
import java.util.HashMap;
16
import java.util.HashSet;
17
import java.util.Map;
18
import java.util.Set;
19

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

    
27
import eu.etaxonomy.cdm.database.PermissionDeniedException;
28
import eu.etaxonomy.cdm.model.CdmBaseType;
29
import eu.etaxonomy.cdm.model.common.CdmBase;
30
import eu.etaxonomy.cdm.model.common.IPublishable;
31
import eu.etaxonomy.cdm.persistence.hibernate.permission.CRUD;
32
import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmPermissionEvaluator;
33
import eu.etaxonomy.cdm.persistence.hibernate.permission.Operation;
34
import eu.etaxonomy.cdm.persistence.hibernate.permission.Role;
35
/**
36
 * @author k.luther
37
 * @author a.kohlbecker
38
 *
39
 */
40
@Component
41
public class CdmSecurityHibernateInterceptor extends EmptyInterceptor {
42

    
43
    private static final long serialVersionUID = 8477758472369568074L;
44

    
45
    public static final Logger logger = Logger.getLogger(CdmSecurityHibernateInterceptor.class);
46

    
47

    
48
    private CdmPermissionEvaluator permissionEvaluator;
49

    
50
    public CdmPermissionEvaluator getPermissionEvaluator() {
51
        return permissionEvaluator;
52
    }
53

    
54
    public void setPermissionEvaluator(CdmPermissionEvaluator permissionEvaluator) {
55
        this.permissionEvaluator = permissionEvaluator;
56
    }
57

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

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

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

    
71
        for ( CdmBaseType type: CdmBaseType.values()){
72
            exculdeMap.put(type.getBaseClass(), new HashSet<String>());
73
            exculdeMap.get(type.getBaseClass()).addAll(defaultExculdes);
74
        }
75
        exculdeMap.put(CdmBase.class, new HashSet<String>());
76
        exculdeMap.get(CdmBase.class).addAll(defaultExculdes);
77

    
78

    
79
        /*
80
         * default fields required for each type for which excludes are defined
81
         */
82
//        exculdeMap.get(TaxonNameBase.class).add("updatedBy");
83
//        exculdeMap.get(TaxonNameBase.class).add("created");
84
//        exculdeMap.get(TaxonNameBase.class).add("updated");
85

    
86
        /*
87
         * the specific excludes
88
         */
89
//        exculdeMap.get(TaxonNameBase.class).add("taxonBases");
90
    }
91

    
92

    
93
    /* (non-Javadoc)
94
     * @see org.hibernate.EmptyInterceptor#onSave(java.lang.Object, java.io.Serializable, java.lang.Object[], java.lang.String[], org.hibernate.type.Type[])
95
     */
96
    @Override
97
    public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] type) {
98

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

    
108

    
109
    /* (non-Javadoc)
110
     * @see org.hibernate.EmptyInterceptor#onFlushDirty(java.lang.Object, java.io.Serializable, java.lang.Object[], java.lang.Object[], java.lang.String[], org.hibernate.type.Type[])
111
     */
112
    @Override
113
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
114

    
115
        if (SecurityContextHolder.getContext().getAuthentication() == null || !(entity instanceof CdmBase)) {
116
            return true;
117
        }
118
        CdmBase cdmEntity = (CdmBase) entity;
119
        if (previousState == null){
120
            return onSave(cdmEntity, id, currentState, propertyNames, null);
121
        }
122
        if (isModified(currentState, previousState, propertyNames, exculdeMap.get(baseType(cdmEntity)))) {
123
            // evaluate throws EvaluationFailedException
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
    private Class<? extends CdmBase> baseType(CdmBase cdmEntity) {
138
        Class<? extends CdmBase> basetype = CdmBaseType.baseTypeFor(cdmEntity.getClass());
139
        return basetype == null ? CdmBase.class : basetype;
140
    }
141

    
142

    
143

    
144
    /* (non-Javadoc)
145
     * @see org.hibernate.EmptyInterceptor#onDelete(java.lang.Object, java.io.Serializable, java.lang.Object[], java.lang.String[], org.hibernate.type.Type[])
146
     */
147
    @Override
148
    public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) {
149

    
150
        if (SecurityContextHolder.getContext().getAuthentication() == null || !(entity instanceof CdmBase)) {
151
            return;
152
        }
153
        CdmBase cdmEntity = (CdmBase) entity;
154
        // evaluate throws EvaluationFailedException
155
        checkPermissions(cdmEntity, Operation.DELETE);
156
        logger.debug("permission check suceeded - object update granted");
157
        return;
158
    }
159

    
160
    /**
161
     * checks if the current authentication has the <code>expectedPermission</code> on the supplied <code>entity</code>.
162
     * Throws an {@link PermissionDeniedException} if the evaluation fails.
163
     *
164
     * @param entity
165
     * @param expectedOperation
166
     */
167
    private void checkPermissions(CdmBase entity, EnumSet<CRUD> expectedOperation) {
168

    
169
        if (!permissionEvaluator.hasPermission(SecurityContextHolder.getContext().getAuthentication(), entity, expectedOperation)){
170
            throw new PermissionDeniedException(SecurityContextHolder.getContext().getAuthentication(), entity, expectedOperation);
171
        }
172
    }
173

    
174
    /**
175
     * checks if the current authentication has at least one of the <code>roles</code>.
176
     * Throws an {@link PermissionDeniedException} if the evaluation fails.
177
     * @param roles
178
     */
179
    private void checkRoles(Role ... roles) {
180

    
181
        if (!permissionEvaluator.hasOneOfRoles(SecurityContextHolder.getContext().getAuthentication(), roles)){
182
            throw new PermissionDeniedException(SecurityContextHolder.getContext().getAuthentication(), roles);
183
        }
184
    }
185

    
186
    /**
187
     * Checks if the CDM entity as been modified by comparing the current with the previous state.
188
     *
189
     * @param currentState
190
     * @param previousState
191
     * @return true if the currentState and previousState differ.
192
     */
193
    private boolean isModified(Object[] currentState, Object[] previousState, String[] propertyNames, Set<String> excludes) {
194

    
195
        Set<Integer> excludeIds = null;
196

    
197
        if(excludes != null && excludes.size() > 0) {
198
            excludeIds = new HashSet<Integer>(excludes.size());
199
            int i = 0;
200
            for(String prop : propertyNames){
201
                if(excludes.contains(prop)){
202
                    excludeIds.add(i);
203
                }
204
                if(excludeIds.size() == excludes.size()){
205
                    // all ids found
206
                    break;
207
                }
208
                i++;
209
            }
210
        }
211

    
212
        for (int i = 0; i<currentState.length; i++){
213
            if((excludeIds == null || !excludeIds.contains(i))){
214
                if(propertyIsModified(currentState, previousState, i)){
215
                    if(logger.isDebugEnabled()){
216
                        logger.debug("modified property found: " + propertyNames[i] + ", previousState: " + previousState[i] + ", currentState: " + currentState[i] );
217
                    }
218
                    return true;
219
                }
220
            }
221
        }
222

    
223
        return false;
224
    }
225

    
226
    /**
227
     * Compares the object states at the property denoted by the key parameter and returns true if they differ in this property
228
     *
229
     * @param currentState
230
     * @param previousState
231
     * @param isModified
232
     * @param key
233
     * @return
234
     */
235
    private boolean propertyIsModified(Object[] currentState, Object[] previousState, int key) {
236
        if (currentState[key]== null ) {
237
            if ( previousState[key]!= null) {
238
                return true;
239
            }
240
        }
241
        if (currentState[key]!= null ){
242
            if (previousState[key] == null){
243
                return true;
244
            }
245
        }
246
        if (currentState[key]!= null && previousState[key] != null){
247
            if (!currentState[key].equals(previousState[key])) {
248
                return true;
249
            }
250
        }
251
        return false;
252
    }
253

    
254
    private boolean namedPropertyIsModified(Object[] currentState, Object[] previousState, String[] propertyNames, String propertyNameToTest) {
255

    
256
        int key = ArrayUtils.indexOf(propertyNames, propertyNameToTest);
257
        return propertyIsModified(currentState, previousState, key);
258
    }
259

    
260

    
261
}
(10-10/20)