Project

General

Profile

Download (9.61 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");
69

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

    
77

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

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

    
91

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

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

    
107

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

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

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

    
136
    private Class<? extends CdmBase> baseType(CdmBase cdmEntity) {
137
        Class<? extends CdmBase> basetype = CdmBaseType.baseTypeFor(cdmEntity.getClass());
138
        return basetype == null ? CdmBase.class : basetype;
139
    }
140

    
141

    
142

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

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

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

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

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

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

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

    
194
        Set<Integer> excludeIds = null;
195

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

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

    
222
        return false;
223
    }
224

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

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

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

    
259

    
260
}
(10-10/20)