Merged latest trunk updates to branch
[cdmlib.git] / cdmlib-persistence / src / main / java / eu / etaxonomy / cdm / persistence / hibernate / permission / voter / CdmPermissionVoter.java
1 // $Id$
2 /**
3 * Copyright (C) 2012 EDIT
4 * European Distributed Institute of Taxonomy
5 * http://www.e-taxonomy.eu
6 *
7 * The contents of this file are subject to the Mozilla Public License Version 1.1
8 * See LICENSE.TXT at the top of this package for the full license terms.
9 */
10 package eu.etaxonomy.cdm.persistence.hibernate.permission.voter;
11
12 import java.util.Collection;
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 sun.security.provider.PolicyParser.ParsingException;
21
22 import eu.etaxonomy.cdm.model.common.CdmBase;
23 import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmAuthority;
24 import eu.etaxonomy.cdm.persistence.hibernate.permission.Operation;
25 import eu.etaxonomy.cdm.persistence.hibernate.permission.CdmPermissionClass;
26
27 /**
28 * The <code>CdmPermissionVoter</code> provides access control votes for {@link CdmBase} objects.
29 *
30 * @author andreas kohlbecker
31 * @date Sep 4, 2012
32 *
33 */
34 public abstract class CdmPermissionVoter implements AccessDecisionVoter {
35
36 public static final Logger logger = Logger.getLogger(CdmPermissionVoter.class);
37
38 /* (non-Javadoc)
39 * @see org.springframework.security.access.AccessDecisionVoter#supports(org.springframework.security.access.ConfigAttribute)
40 */
41 public boolean supports(ConfigAttribute attribute) {
42 // all CdmPermissionVoter support CdmAuthority
43 return attribute instanceof CdmAuthority;
44 }
45
46 /* (non-Javadoc)
47 * @see org.springframework.security.access.AccessDecisionVoter#supports(java.lang.Class)
48 */
49 public boolean supports(Class<?> clazz) {
50 /* NOTE!!!
51 * Do not change this, all CdmPermissionVoters must support CdmBase.class
52 */
53 return clazz.isInstance(CdmBase.class);
54 }
55
56 /**
57 * Sets the Cdm type, or super type this Voter is responsible for.
58 */
59 abstract public Class<? extends CdmBase> getResponsibilityClass();
60
61
62 protected boolean isResponsibleFor(Object securedObject) {
63 return getResponsibilityClass().isAssignableFrom(securedObject.getClass());
64 }
65
66 protected boolean isResponsibleFor(CdmPermissionClass permissionClass) {
67 return getResponsibility().equals(permissionClass);
68 }
69
70 /**
71 * Get the according CdmPermissionClass matching {@link #getResponsibilityClass()} the cdm class this voter is responsible for.
72 * @return
73 */
74 protected CdmPermissionClass getResponsibility() {
75 return CdmPermissionClass.getValueOf(getResponsibilityClass());
76 }
77
78 /* (non-Javadoc)
79 * @see org.springframework.security.access.AccessDecisionVoter#vote(org.springframework.security.core.Authentication, java.lang.Object, java.util.Collection)
80 */
81 public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
82
83 if(!isResponsibleFor(object)){
84 logger.debug("class missmatch => ACCESS_ABSTAIN");
85 return ACCESS_ABSTAIN;
86 }
87
88 if (logger.isDebugEnabled()){
89 logger.debug("authentication: " + authentication.getName() + ", object : " + object.toString() + ", attribute[0]:" + ((CdmAuthority)attributes.iterator().next()).getAttribute());
90 }
91
92 int fallThroughVote = ACCESS_DENIED;
93
94 // loop over all attributes = permissions of which at least one must match
95 // usually there is only one element in the collection!
96 for(ConfigAttribute attribute : attributes){
97 if(!(attribute instanceof CdmAuthority)){
98 throw new RuntimeException("attributes must contain only CdmAuthority");
99 }
100 CdmAuthority evalPermission = (CdmAuthority)attribute;
101
102 for (GrantedAuthority authority: authentication.getAuthorities()){
103
104 CdmAuthority auth;
105 try {
106 auth = CdmAuthority.fromGrantedAuthority(authority);
107 } catch (ParsingException e) {
108 logger.debug("skipping " + authority.getAuthority() + " due to ParsingException");
109 continue;
110 }
111
112 // check if the voter is responsible for the permission to be evaluated
113 if( ! isResponsibleFor(evalPermission.getPermissionClass())){
114 logger.debug(getResponsibility() + " not responsible for " + evalPermission.getPermissionClass() + " -> skipping");
115 continue;
116 }
117
118 ValidationResult vr = new ValidationResult();
119
120 boolean isALL = auth.getPermissionClass().equals(CdmPermissionClass.ALL);
121
122 vr.isClassMatch = isALL || auth.getPermissionClass().equals(evalPermission.getPermissionClass());
123 vr.isPermissionMatch = auth.getOperation().containsAll(evalPermission.getOperation());
124 vr.isUuidMatch = auth.hasTargetUuid() && auth.getTargetUUID().equals(((CdmBase)object).getUuid());
125
126 //
127 // only vote if no property is defined.
128 // Authorities with properties must be voted by type specific voters.
129 //
130 if(!auth.hasProperty()){
131 if ( !auth.hasTargetUuid() && vr.isClassMatch && vr.isPermissionMatch){
132 logger.debug("no tragetUuid, 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 //
145 if(vr.isClassMatch){
146 fallThroughVote = ACCESS_ABSTAIN;
147 }
148 }
149
150 //
151 // ask subclasses for further voting decisions
152 // subclasses will cast votes for specific Cdm Types
153 //
154 Integer furtherVotingResult = furtherVotingDescisions(auth, object, attributes, vr);
155 if(furtherVotingResult != null && furtherVotingResult != ACCESS_ABSTAIN){
156 logger.debug("furtherVotingResult => " + furtherVotingResult);
157 return furtherVotingResult;
158 }
159
160 } // END Authorities loop
161 } // END attributes loop
162
163 // the value of fallThroughVote depends on whether the authority had an property or not, see above
164 logger.debug("fallThroughVote => " + fallThroughVote);
165 return fallThroughVote;
166 }
167
168 /**
169 * Override this method to implement specific decisions.
170 * Implementations of this method will be executed in {@link #vote(Authentication, Object, Collection)}.
171 *
172 * @param CdmAuthority
173 * @param object
174 * @param attributes
175 * @param validationResult
176 * @return A return value of ACCESS_ABSTAIN or null will be ignored in {@link #vote(Authentication, Object, Collection)}
177 */
178 protected Integer furtherVotingDescisions(CdmAuthority CdmAuthority, Object object, Collection<ConfigAttribute> attributes,
179 ValidationResult validationResult) {
180 return null;
181 }
182
183 /**
184 * Holds various flags with validation results.
185 * Is used to pass this information from
186 * {@link CdmPermissionVoter#vote(Authentication, Object, Collection)}
187 * to {@link CdmPermissionVoter#furtherVotingDescisions(CdmAuthority, Object, Collection, ValidationResult)}
188 *
189 * @author andreas kohlbecker
190 * @date Sep 5, 2012
191 *
192 */
193 protected class ValidationResult {
194 boolean isPermissionMatch = false;
195 boolean isPropertyMatch = false;
196 boolean isUuidMatch = false;
197 boolean isClassMatch = false;
198 }
199
200 }