Project

General

Profile

Download (11.6 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2012 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.permission.voter;
10

    
11
import java.util.Collection;
12
import java.util.EnumSet;
13

    
14
import org.apache.log4j.Logger;
15
import org.springframework.security.access.AccessDecisionVoter;
16
import org.springframework.security.access.ConfigAttribute;
17
import org.springframework.security.core.Authentication;
18
import org.springframework.security.core.GrantedAuthority;
19

    
20
import eu.etaxonomy.cdm.model.common.CdmBase;
21
import eu.etaxonomy.cdm.persistence.hibernate.permission.CRUD;
22
import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmAuthority;
23
import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmAuthorityParsingException;
24
import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmPermissionClass;
25

    
26
/**
27
 * The <code>CdmPermissionVoter</code> provides access control votes for {@link CdmBase} objects.
28
 *
29
 * @author andreas kohlbecker
30
 \* @since Sep 4, 2012
31
 *
32
 */
33
public abstract class CdmPermissionVoter implements AccessDecisionVoter <CdmBase> {
34

    
35
    /**
36
     *
37
     */
38
    private static final EnumSet<CRUD> DELETE = EnumSet.of(CRUD.DELETE);
39
    public static final Logger logger = Logger.getLogger(CdmPermissionVoter.class);
40

    
41
    @Override
42
    public boolean supports(ConfigAttribute attribute) {
43
        // all CdmPermissionVoter support CdmAuthority
44
        return attribute instanceof CdmAuthority;
45
    }
46

    
47
    @Override
48
    public boolean supports(Class<?> clazz) {
49
        /* NOTE!!!
50
         * Do not change this, all CdmPermissionVoters must support CdmBase.class
51
         */
52
        return clazz.isInstance(CdmBase.class);
53
    }
54

    
55
    /**
56
     * Sets the Cdm type, or super type this Voter is responsible for.
57
     */
58
    abstract public Class<? extends CdmBase> getResponsibilityClass();
59

    
60

    
61
    protected boolean isResponsibleFor(Object securedObject) {
62
        return getResponsibilityClass().isAssignableFrom(securedObject.getClass());
63
    }
64

    
65
    protected boolean isResponsibleFor(CdmPermissionClass permissionClass) {
66
        return getResponsibility().equals(permissionClass);
67
    }
68

    
69
    /**
70
     * Get the according CdmPermissionClass matching {@link #getResponsibilityClass()} the cdm class this voter is responsible for.
71
     * @return
72
     */
73
    protected CdmPermissionClass getResponsibility() {
74
        return CdmPermissionClass.getValueOf(getResponsibilityClass());
75
    }
76

    
77
    @Override
78
    public int vote(Authentication authentication, CdmBase cdmBase, Collection<ConfigAttribute> attributes) {
79

    
80
        if(!isResponsibleFor(cdmBase)){
81
            logger.debug(voterLoggingLabel() + " class missmatch => ACCESS_ABSTAIN");
82
            return ACCESS_ABSTAIN;
83
        }
84

    
85
        if (logger.isDebugEnabled()){
86
            logger.debug(voterLoggingLabel() + " voting for authentication: " + authentication.getName() + ", object : " + cdmBase.toString() + ", attribute[0]:" + ((CdmAuthority)attributes.iterator().next()).getAttribute());
87
        }
88

    
89
        int fallThroughVote = ACCESS_DENIED;
90
        boolean deniedByPreviousFurtherVoting = false;
91

    
92
        // loop over all attributes = permissions of which at least one must match
93
        // usually there is only one element in the collection!
94
        for(ConfigAttribute attribute : attributes){
95
            if(!(attribute instanceof CdmAuthority)){
96
                throw new RuntimeException("attributes must contain only CdmAuthority");
97
            }
98
            CdmAuthority evalPermission = (CdmAuthority)attribute;
99

    
100
            for (GrantedAuthority authority: authentication.getAuthorities()){
101

    
102
                CdmAuthority auth;
103
                try {
104
                    auth = CdmAuthority.fromGrantedAuthority(authority);
105
                } catch (CdmAuthorityParsingException e) {
106
                    logger.debug(voterLoggingLabel() + " skipping " + authority.getAuthority() + " due to CdmAuthorityParsingException");
107
                    continue;
108
                }
109

    
110
                // check if the voter is responsible for the permission to be evaluated
111
                if( ! isResponsibleFor(evalPermission.getPermissionClass())){
112
                    logger.debug(voterLoggingLabel() + " not responsible for " + evalPermission.getPermissionClass() + " -> skipping");
113
                    continue;
114
                }
115

    
116
                ValidationResult vr = new ValidationResult();
117

    
118
                boolean isALL = auth.getPermissionClass().equals(CdmPermissionClass.ALL);
119

    
120
                vr.isClassMatch = isALL || auth.getPermissionClass().equals(evalPermission.getPermissionClass());
121
                vr.isPermissionMatch = auth.getOperation().containsAll(evalPermission.getOperation());
122
                vr.isUuidMatch = auth.hasTargetUuid() && auth.getTargetUUID().equals(cdmBase.getUuid());
123
                vr.isIgnoreUuidMatch = !auth.hasTargetUuid();
124

    
125
                if(logger.isDebugEnabled()){
126
                    logger.debug(voterLoggingLabel() + " " + vr);
127
                }
128

    
129
                // first of all, always allow deleting orphan entities
130
                if(vr.isClassMatch && evalPermission.getOperation().equals(DELETE) && isOrpahn(cdmBase)) {
131
                    if(logger.isDebugEnabled()){
132
                        logger.debug(voterLoggingLabel() +" entity is considered orphan => ACCESS_GRANTED");
133
                    }
134
                    return ACCESS_GRANTED;
135
                }
136

    
137
                if(!auth.hasProperty()){
138
                    if ( vr.isIgnoreUuidMatch && vr.isClassMatch && vr.isPermissionMatch){
139
                        if(logger.isDebugEnabled()){
140
                            logger.debug(voterLoggingLabel() +" no targetUuid, class & permission match => ACCESS_GRANTED");
141
                        }
142
                        return ACCESS_GRANTED;
143
                    }
144
                    if ( vr.isUuidMatch && vr.isClassMatch && vr.isPermissionMatch ){
145
                        if(logger.isDebugEnabled()){
146
                            logger.debug(voterLoggingLabel() +" permission, class and uuid are matching => ACCESS_GRANTED");
147
                        }
148
                        return ACCESS_GRANTED;
149
                    }
150
                } else {
151
                    //
152
                    // If the authority contains a property AND the voter is responsible for this class
153
                    // we must change the fallThroughVote
154
                    // to ABSTAIN, since no decision can be made in this case at this point
155
                    // the decision will be delegated to the furtherVotingDescisions() method
156
                    if(vr.isClassMatch){
157
                        fallThroughVote = ACCESS_ABSTAIN;
158
                    }
159
                }
160

    
161

    
162
                //
163
                // ask subclasses for further voting decisions
164
                // subclasses will cast votes for specific Cdm Types
165
                //
166
                Integer furtherVotingResult = furtherVotingDescisions(auth, cdmBase, attributes, vr);
167
                if(furtherVotingResult != null){
168
                    if(logger.isDebugEnabled()){
169
                        logger.debug(voterLoggingLabel() + " furtherVotingResult => " + voteToString(furtherVotingResult));
170
                    }
171
                    switch(furtherVotingResult){
172
                        case ACCESS_GRANTED:
173
                            // no further check needed
174
                            return ACCESS_GRANTED;
175
                        case ACCESS_DENIED:
176
                            // remember the DENIED vote in case none of
177
                            // potentially following furtherVotes are
178
                            // GRANTED
179
                            deniedByPreviousFurtherVoting = true;
180
                        //$FALL-THROUGH$
181
                        case ACCESS_ABSTAIN: /* nothing to do */
182
                            default: /* nothing to do */
183
                    }
184
                }
185

    
186
            } // END Authorities loop
187
        } // END attributes loop
188

    
189
        int votingResult = deniedByPreviousFurtherVoting ? ACCESS_DENIED : fallThroughVote;
190
        // the value of fallThroughVote depends on whether the authority had an property or not, see above
191
        if(logger.isDebugEnabled()){
192
            logger.debug(voterLoggingLabel() + " fallThroughVote => " + voteToString(fallThroughVote));
193
            logger.debug(voterLoggingLabel() + " ##votingResult## => " + voteToString(votingResult));
194
        }
195
        return votingResult;
196
    }
197

    
198
    /**
199
     * The AccessDecisionVoter implementing this method can indicate via this method that
200
     * an entity has become orphan in order to allow deleting it. In case the implementing method
201
     * returns <code>false</code> deleting of the entity will be denied.
202
     * <p>
203
     * This is important
204
     * in the context of hierarchic permission propagation like for example in
205
     * tree structures where the permission to delete an entity is given on base
206
     * of the permission on an parent object. Entities which become detached
207
     * from the tree would otherwise no longer be deletable.
208
     *
209
     * @param object
210
     * @return whether the cdm entity is orpahn
211
     */
212
    public abstract boolean isOrpahn(CdmBase object);
213

    
214
    /**
215
     * Override this method to implement specific decisions.
216
     * Implementations of this method will be executed in {@link #vote(Authentication, Object, Collection)}.
217
     *
218
     * @param CdmAuthority
219
     * @param object
220
     * @param attributes
221
     * @param validationResult
222
     * @return A return value of ACCESS_ABSTAIN or null will be ignored in {@link #vote(Authentication, Object, Collection)}
223
     */
224
    protected Integer furtherVotingDescisions(CdmAuthority CdmAuthority, Object object, Collection<ConfigAttribute> attributes,
225
            ValidationResult validationResult) {
226
        return null;
227
    }
228

    
229
    /**
230
     * returns a label for the logging output
231
     * @return
232
     */
233
    protected String voterLoggingLabel(){
234
        return "(" + getResponsibilityClass().getSimpleName() + "-Voter)";
235
    }
236

    
237
    /**
238
     *
239
     * @param vote
240
     * @return string representations for the votes defined in {@link AccessDecisionVoter}
241
     */
242
    protected String voteToString(int vote) {
243
        switch (vote){
244
            case 1: return "ACCESS_GRANTED";
245
            case 0: return "ACCESS_ABSTAIN";
246
            case -1: return "ACCESS_DENIED";
247
            default: return Integer.toString(vote);
248
        }
249
    }
250

    
251

    
252
    /**
253
     * Holds various flags with validation results.
254
     * Is used to pass this information from
255
     * {@link CdmPermissionVoter#vote(Authentication, Object, Collection)}
256
     * to {@link CdmPermissionVoter#furtherVotingDescisions(CdmAuthority, Object, Collection, ValidationResult)}
257
     *
258
     * @author andreas kohlbecker
259
     * @since Sep 5, 2012
260
     *
261
     */
262
    protected class ValidationResult {
263

    
264
        /**
265
         * ignore the result of the uuid match test completely
266
         * this flag becomes true when the authority given to
267
         * an authentication has no uuid part
268
         */
269
        public boolean isIgnoreUuidMatch;
270
        boolean isPermissionMatch = false;
271
        boolean isPropertyMatch = false;
272
        boolean isUuidMatch = false;
273
        boolean isClassMatch = false;
274

    
275
        @Override
276
        public String toString(){
277
            return "isClassMatch: " + Boolean.toString(isClassMatch) + ", "
278
                    + "isUuidMatch: " + Boolean.toString(isUuidMatch) + ", "
279
                    + "isPermissionMatch: " + Boolean.toString(isPermissionMatch) + ", "
280
                    + "isPropertyMatch: " + Boolean.toString(isPropertyMatch);
281

    
282
        }
283
    }
284

    
285
}
(1-1/12)