001 /*
002 * Copyright 2007 Robin Helgelin
003 * Copyright 2008 Jonathan Barker
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package nu.localhost.tapestry.acegi.components;
019
020 import java.util.Arrays;
021 import java.util.Collection;
022 import java.util.Collections;
023 import java.util.HashSet;
024 import java.util.Iterator;
025 import java.util.Set;
026
027 import org.acegisecurity.Authentication;
028 import org.acegisecurity.GrantedAuthority;
029 import org.acegisecurity.GrantedAuthorityImpl;
030 import org.acegisecurity.context.SecurityContextHolder;
031 import org.apache.tapestry.Block;
032 import org.apache.tapestry.annotations.Parameter;
033 import org.springframework.util.StringUtils;
034
035 /**
036 * Render it's body depending whether the user is in a specific role or not.
037 *
038 * @author Jonathan Barker
039 * @author Robin Helgelin
040 * @author Tapestry Project (doc comments)
041 */
042 public class IfRole {
043
044 /**
045 * If the logged in user matches this role, then the body of the IfRole component is rendered. If false, the body is
046 * omitted. This is retained for backward compatibility, and corresponds to a single entry in ifAnyGranted
047 */
048 @Parameter(required = false, defaultPrefix = "literal", principal=true)
049 private String role;
050
051 /**
052 * A comma-separated list of roles is supplied to one or more of the
053 * following parameters. If none are supplied, the default behavior is to
054 * permit access. Behavior should be self-explanatory.
055 */
056 @Parameter(required = false, defaultPrefix = "literal")
057 private String ifAllGranted;
058
059 @Parameter(required = false, defaultPrefix = "literal")
060 private String ifAnyGranted;
061
062 @Parameter(required = false, defaultPrefix = "literal")
063 private String ifNotGranted;
064
065 /**
066 * Optional parameter to invert the test. If true, then the body is rendered when the test
067 * parameter is false (not true).
068 */
069 @Parameter
070 private boolean negate;
071
072 /**
073 * An alternate {@link Block} to render if the test parameter is false. The default, null, means
074 * render nothing in that situation.
075 */
076 @Parameter(name = "else")
077 private Block elseBlock;
078
079 private boolean test;
080
081 @SuppressWarnings("unchecked")
082 private Collection getPrincipalAuthorities() {
083 Authentication currentUser = null;
084 currentUser = SecurityContextHolder.getContext().getAuthentication();
085
086 if (null == currentUser) {
087 return Collections.EMPTY_LIST;
088 }
089
090 if ((null == currentUser.getAuthorities()) || (currentUser.getAuthorities().length < 1)) {
091 return Collections.EMPTY_LIST;
092 }
093
094 Collection granted = Arrays.asList(currentUser.getAuthorities());
095 return granted;
096 }
097
098 @SuppressWarnings("unchecked")
099 private Set authoritiesToRoles(Collection c) {
100 Set target = new HashSet();
101
102 for (Iterator iterator = c.iterator(); iterator.hasNext();) {
103 GrantedAuthority authority = (GrantedAuthority) iterator.next();
104
105 if (null == authority.getAuthority()) {
106 throw new IllegalArgumentException(
107 "Cannot process GrantedAuthority objects which return null from getAuthority() - attempting to process "
108 + authority.toString());
109 }
110
111 target.add(authority.getAuthority());
112 }
113
114 return target;
115 }
116
117 @SuppressWarnings("unchecked")
118 private Set parseAuthoritiesString(String authorizationsString) {
119 final Set requiredAuthorities = new HashSet();
120 final String[] authorities = StringUtils.commaDelimitedListToStringArray(authorizationsString);
121
122 for (int i = 0; i < authorities.length; i++) {
123 String authority = authorities[i];
124
125 // Remove the role's whitespace characters without depending on JDK 1.4+
126 // Includes space, tab, new line, carriage return and form feed.
127 String role = StringUtils.replace(authority, " ", "");
128 role = StringUtils.replace(role, "\t", "");
129 role = StringUtils.replace(role, "\r", "");
130 role = StringUtils.replace(role, "\n", "");
131 role = StringUtils.replace(role, "\f", "");
132
133 requiredAuthorities.add(new GrantedAuthorityImpl(role));
134 }
135
136 return requiredAuthorities;
137 }
138
139 /**
140 * Find the common authorities between the current authentication's {@link
141 * GrantedAuthority} and the ones that have been specified in the tag's
142 * ifAny, ifNot or ifAllGranted attributes.
143 *
144 * <p>
145 * We need to manually iterate over both collections, because the granted
146 * authorities might not implement {@link Object#equals(Object)} and
147 * {@link Object#hashCode()} in the same way as {@link
148 * GrantedAuthorityImpl}, thereby invalidating {@link
149 * Collection#retainAll(java.util.Collection)} results.
150 * </p>
151 *
152 * <p>
153 * <strong>CAVEAT</strong>: This method <strong>will not</strong> work if
154 * the granted authorities returns a <code>null</code> string as the
155 * return value of {@link
156 * org.acegisecurity.GrantedAuthority#getAuthority()}.
157 * </p>
158 *
159 * <p>
160 * Reported by rawdave, on Fri Feb 04, 2005 2:11 pm in the Acegi Security
161 * System for Spring forums.
162 * </p>
163 *
164 * @param granted
165 * The authorities granted by the authentication. May be any
166 * implementation of {@link GrantedAuthority} that does
167 * <strong>not</strong> return <code>null</code> from {@link
168 * org.acegisecurity.GrantedAuthority#getAuthority()}.
169 * @param required
170 * A {@link Set} of {@link GrantedAuthorityImpl}s that have been
171 * built using ifAny, ifAll or ifNotGranted.
172 *
173 * @return A set containing only the common authorities between <var>granted</var>
174 * and <var>required</var>.
175 *
176 */
177 @SuppressWarnings("unchecked")
178 private Set retainAll(final Collection granted, final Set required) {
179 Set grantedRoles = authoritiesToRoles(granted);
180 Set requiredRoles = authoritiesToRoles(required);
181 grantedRoles.retainAll(requiredRoles);
182
183 return rolesToAuthorities(grantedRoles, granted);
184 }
185
186 /**
187 * @param grantedRoles
188 * @param granted
189 * @return a Set of Authorities corresponding to the roles in the grantedRoles
190 * that are also in the granted Set of Authorities
191 */
192 @SuppressWarnings("unchecked")
193 private Set rolesToAuthorities(Set grantedRoles, Collection granted) {
194 Set target = new HashSet();
195
196 for (Iterator iterator = grantedRoles.iterator(); iterator.hasNext();) {
197 String role = (String) iterator.next();
198
199 for (Iterator grantedIterator = granted.iterator(); grantedIterator.hasNext();) {
200 GrantedAuthority authority = (GrantedAuthority) grantedIterator.next();
201
202 if (authority.getAuthority().equals(role)) {
203 target.add(authority);
204 break;
205 }
206 }
207 }
208
209 return target;
210 }
211
212 /**
213 * @return false as the default. Returns true if all non-null role expressions are
214 * satisfied. Typically, only one will be used, but if more than one are used, then
215 * the conditions are effectively AND'd
216 */
217 @SuppressWarnings("unchecked")
218 private boolean checkPermission() {
219 if (((null == ifAllGranted) || "".equals(ifAllGranted))
220 && ((null == ifAnyGranted) || "".equals(ifAnyGranted))
221 && ((null == role) || "".equals(role))
222 && ((null == ifNotGranted) || "".equals(ifNotGranted))) {
223 return false;
224 }
225
226 final Collection granted = getPrincipalAuthorities();
227
228 if ((null != role) && !"".equals(role)) {
229 Set grantedCopy = retainAll(granted, parseAuthoritiesString(role));
230 if (grantedCopy.isEmpty()) {
231 return false;
232 }
233 }
234
235 if ((null != ifNotGranted) && !"".equals(ifNotGranted)) {
236 Set grantedCopy = retainAll(granted, parseAuthoritiesString(ifNotGranted));
237
238 if (!grantedCopy.isEmpty()) {
239 return false;
240 }
241 }
242
243 if ((null != ifAllGranted) && !"".equals(ifAllGranted)) {
244 if (!granted.containsAll(parseAuthoritiesString(ifAllGranted))) {
245 return false;
246 }
247 }
248
249 if ((null != ifAnyGranted) && !"".equals(ifAnyGranted)) {
250 Set grantedCopy = retainAll(granted, parseAuthoritiesString(ifAnyGranted));
251
252 if (grantedCopy.isEmpty()) {
253 return false;
254 }
255 }
256
257 return true;
258 }
259
260
261 void setupRender() {
262 test = checkPermission();
263 }
264
265 /**
266 * Returns null if the test method returns true, which allows normal
267 * rendering (of the body). If the test parameter is false, returns the else
268 * parameter (this may also be null).
269 */
270 Object beginRender() {
271 return test != negate ? null : elseBlock;
272 }
273
274 /**
275 * If the test method returns true, then the body is rendered, otherwise not. The component does
276 * not have a template or do any other rendering besides its body.
277 */
278 boolean beforeRenderBody() {
279 return test != negate;
280 }
281 }