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    }