Project

General

Profile

Download (9.68 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
 * @date 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("class missmatch => ACCESS_ABSTAIN");
82
            return ACCESS_ABSTAIN;
83
        }
84

    
85
        if (logger.isDebugEnabled()){
86
            logger.debug("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("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(getResponsibility() + " 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
                // first of all, always allow deleting orphan entities
126
                if(vr.isClassMatch && evalPermission.getOperation().equals(DELETE) && isOrpahn(cdmBase)) {
127
                    return ACCESS_GRANTED;
128
                }
129

    
130
                if(!auth.hasProperty()){
131
                    if ( vr.isIgnoreUuidMatch && vr.isClassMatch && vr.isPermissionMatch){
132
                        logger.debug("no targetUuid, class & permission match => ACCESS_GRANTED");
133
                        return ACCESS_GRANTED;
134
                    }
135
                    if ( vr.isUuidMatch && vr.isClassMatch && vr.isPermissionMatch ){
136
                        logger.debug("permission, class and uuid are matching => ACCESS_GRANTED");
137
                        return ACCESS_GRANTED;
138
                    }
139
                } else {
140
                    //
141
                    // If the authority contains a property AND the voter is responsible for this class
142
                    // we must change the fallThroughVote
143
                    // to ABSTAIN, since no decision can be made in this case at this point
144
                    // the decision will be delegated to the furtherVotingDescisions() method
145
                    if(vr.isClassMatch){
146
                        fallThroughVote = ACCESS_ABSTAIN;
147
                    }
148
                }
149

    
150

    
151
                //
152
                // ask subclasses for further voting decisions
153
                // subclasses will cast votes for specific Cdm Types
154
                //
155
                Integer furtherVotingResult = furtherVotingDescisions(auth, cdmBase, attributes, vr);
156
                if(furtherVotingResult != null){
157
                    logger.debug("furtherVotingResult => " + furtherVotingResult);
158
                    switch(furtherVotingResult){
159
                        case ACCESS_GRANTED:
160
                            // no further check needed
161
                            return ACCESS_GRANTED;
162
                        case ACCESS_DENIED:
163
                            // remember the DENIED vote in case none of
164
                            // potentially following furtherVotes are
165
                            // GRANTED
166
                            deniedByPreviousFurtherVoting = true;
167
                        //$FALL-THROUGH$
168
                        case ACCESS_ABSTAIN: /* nothing to do */
169
                            default: /* nothing to do */
170
                    }
171
                }
172

    
173
            } // END Authorities loop
174
        } // END attributes loop
175

    
176
        // the value of fallThroughVote depends on whether the authority had an property or not, see above
177
        logger.debug("fallThroughVote => " + fallThroughVote);
178
        return deniedByPreviousFurtherVoting ? ACCESS_DENIED : fallThroughVote;
179
    }
180

    
181
    /**
182
     * The AccessDecisionVoter implementing this method can indicate via this method that
183
     * an entity has become orphan in order to allow deleting it. In case the implementing method
184
     * returns <code>false</code> deleting of the entity will be denied.
185
     * <p>
186
     * This is important
187
     * in the context of hierarchic permission propagation like for example in
188
     * tree structures where the permission to delete an entity is given on base
189
     * of the permission on an parent object. Entities which become detached
190
     * from the tree would otherwise no longer be deletable.
191
     *
192
     * @param object
193
     * @return whether the cdm entity is orpahn
194
     */
195
    public abstract boolean isOrpahn(CdmBase object);
196

    
197
    /**
198
     * Override this method to implement specific decisions.
199
     * Implementations of this method will be executed in {@link #vote(Authentication, Object, Collection)}.
200
     *
201
     * @param CdmAuthority
202
     * @param object
203
     * @param attributes
204
     * @param validationResult
205
     * @return A return value of ACCESS_ABSTAIN or null will be ignored in {@link #vote(Authentication, Object, Collection)}
206
     */
207
    protected Integer furtherVotingDescisions(CdmAuthority CdmAuthority, Object object, Collection<ConfigAttribute> attributes,
208
            ValidationResult validationResult) {
209
        return null;
210
    }
211

    
212
    /**
213
     * Holds various flags with validation results.
214
     * Is used to pass this information from
215
     * {@link CdmPermissionVoter#vote(Authentication, Object, Collection)}
216
     * to {@link CdmPermissionVoter#furtherVotingDescisions(CdmAuthority, Object, Collection, ValidationResult)}
217
     *
218
     * @author andreas kohlbecker
219
     * @since Sep 5, 2012
220
     *
221
     */
222
    protected class ValidationResult {
223

    
224
        /**
225
         * ignore the result of the uuid match test completely
226
         * this flag becomes true when the authority given to
227
         * an authentication has no uuid part
228
         */
229
        public boolean isIgnoreUuidMatch;
230
        boolean isPermissionMatch = false;
231
        boolean isPropertyMatch = false;
232
        boolean isUuidMatch = false;
233
        boolean isClassMatch = false;
234
    }
235

    
236
}
(1-1/12)