001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  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 org.apache.geronimo.j2ee.deployment.annotation;
019    
020    import java.util.List;
021    
022    import javax.annotation.security.DeclareRoles;
023    import javax.annotation.security.RunAs;
024    import javax.servlet.Servlet;
025    
026    import org.apache.commons.logging.Log;
027    import org.apache.commons.logging.LogFactory;
028    import org.apache.geronimo.common.DeploymentException;
029    import org.apache.geronimo.xbeans.javaee.RoleNameType;
030    import org.apache.geronimo.xbeans.javaee.RunAsType;
031    import org.apache.geronimo.xbeans.javaee.SecurityRoleType;
032    import org.apache.geronimo.xbeans.javaee.ServletType;
033    import org.apache.geronimo.xbeans.javaee.ServletNameType;
034    import org.apache.geronimo.xbeans.javaee.FullyQualifiedClassType;
035    import org.apache.geronimo.xbeans.javaee.WebAppType;
036    import org.apache.xbean.finder.ClassFinder;
037    
038    
039    /**
040     * Static helper class used to encapsulate all the functions related to the translation of
041     * <strong>@DeclareRoles</strong> and <strong>@RunAs</strong> annotations to deployment
042     * descriptor tags. The SecurityAnnotationHelper class can be used as part of the deployment of a
043     * module into the Geronimo server. It performs the following major functions:
044     *
045     * <ol>
046     *      <li>Translates annotations into corresponding deployment descriptor elements (so that the
047     *          actual deployment descriptor in the module can be updated or even created if necessary)
048     * </ol>
049     *
050     * <p><strong>Note(s):</strong>
051     * <ul>
052     *      <li>Supports only servlets
053     *      <li>The user is responsible for invoking change to metadata-complete
054     *      <li>This helper class will validate any changes it makes to the deployment descriptor. An
055     *          exception will be thrown if it fails to parse
056     * </ul>
057     *
058     * @version $Rev $Date
059     * @since 04-2007
060     */
061    public final class SecurityAnnotationHelper extends AnnotationHelper {
062    
063        // Private instance variables
064        private static final Log log = LogFactory.getLog(SecurityAnnotationHelper.class);
065    
066        // Private constructor to prevent instantiation
067        private SecurityAnnotationHelper() {
068        }
069    
070        /**
071         * Update the deployment descriptor from the DeclareRoles and RunAs annotations
072         *
073         * @param webApp Access to the spec dd
074         * @param classFinder  Access to the classes of interest
075         * @throws DeploymentException if parsing or validation error
076         */
077        public static void processAnnotations(WebAppType webApp, ClassFinder classFinder) throws DeploymentException {
078            if (webApp != null && classFinder != null) {
079                if (classFinder.isAnnotationPresent(DeclareRoles.class)) {
080                    processDeclareRoles(webApp, classFinder);
081                }
082                if (classFinder.isAnnotationPresent(RunAs.class)) {
083                    processRunAs(webApp, classFinder);
084                }
085            }
086        }
087    
088    
089        /**
090         * Process @DeclareRole annotations (for servlets only)
091         *
092         * @param webApp Access to the spec dd
093         * @param classFinder Access to the classes of interest
094         * @throws DeploymentException if parsing or validation error
095         */
096        private static void processDeclareRoles(WebAppType webApp, ClassFinder classFinder) throws DeploymentException {
097            log.debug("processDeclareRoles(): Entry: webApp: " + webApp.toString());
098    
099            List<Class> classesWithDeclareRoles = classFinder.findAnnotatedClasses(DeclareRoles.class);
100    
101            // Class-level annotation
102            for (Class cls : classesWithDeclareRoles) {
103                DeclareRoles declareRoles = (DeclareRoles) cls.getAnnotation(DeclareRoles.class);
104                if (declareRoles != null && Servlet.class.isAssignableFrom(cls)) {
105                    addDeclareRoles(webApp, declareRoles, cls);
106                }
107            }
108    
109            // Validate deployment descriptor to ensure it's still okay
110            validateDD(new AnnotatedWebApp(webApp));
111    
112            log.debug("processDeclareRoles(): Exit: webApp: " + webApp.toString());
113        }
114    
115    
116        /**
117         * Process @RunAs annotations (for servlets only)
118         *
119         * @param webApp Access to the spec dd
120         * @param classFinder Access to the classes of interest
121         * @throws DeploymentException if parsing or validation error
122         */
123        private static void processRunAs(WebAppType webApp, ClassFinder classFinder) throws DeploymentException {
124            log.debug("processRunAs(): Entry: webApp: " + webApp.toString());
125    
126            List<Class> classesWithRunAs = classFinder.findAnnotatedClasses(RunAs.class);
127    
128            // Class-level annotation
129            for (Class cls : classesWithRunAs) {
130                RunAs runAs = (RunAs) cls.getAnnotation(RunAs.class);
131                if (runAs != null && Servlet.class.isAssignableFrom(cls)) {
132                    addRunAs(webApp, runAs, cls);
133                }
134            }
135    
136            // Validate deployment descriptor to ensure it's still okay
137            validateDD(new AnnotatedWebApp(webApp));
138    
139            log.debug("processRunAs(): Exit: webApp: " + webApp.toString());
140        }
141    
142    
143        /**
144         * Add @DeclareRoles annotations to the deployment descriptor. XMLBeans are used to read and
145         * manipulate the deployment descriptor as necessary. The DeclareRoles annotation(s) will be
146         * converted to one of the following deployment descriptors:
147         *
148         * <ol>
149         *      <li><security-role> -- Describes a single security role
150         * </ol>
151         *
152         * <p><strong>Note(s):</strong>
153         * <ul>
154         *      <li>The deployment descriptor is the authoritative source so this method ensures that
155         *          existing elements in it are not overwritten by annoations
156         * </ul>
157         *
158         * @param webApp  Access to the spec dd
159         * @param annotation    @DeclareRoles annotation
160         * @param cls           Class name with the @DeclareRoles annoation
161         */
162        private static void addDeclareRoles(WebAppType webApp, DeclareRoles annotation, Class cls) {
163            log.debug("addDeclareRoles( [webApp] " + webApp.toString() + "," + '\n' +
164                      "[annotation] " + annotation.toString() + "," + '\n' +
165                      "[cls] " + (cls != null ? cls.getName() : null) + "): Entry");
166    
167            // Get all the <security-role> tags from the deployment descriptor
168            SecurityRoleType[] securityRoles = webApp.getSecurityRoleArray();
169    
170            String[] annotationRoleNames = annotation.value();
171            for (String annotationRoleName : annotationRoleNames) {
172                if (!annotationRoleName.equals("")) {
173                    boolean exists = false;
174                    for (SecurityRoleType securityRole : securityRoles) {
175                        if (securityRole.getRoleName().getStringValue().trim().equals(annotationRoleName)) {
176                            exists = true;
177                            break;
178                        }
179                    }
180                    if (exists) {
181                        log.debug("addDeclareRoles: <security-role> entry found: " + annotationRoleName);
182                    }
183                    else {
184                        log.debug("addDeclareRoles: <security-role> entry NOT found: " + annotationRoleName);
185                        SecurityRoleType securityRole = webApp.addNewSecurityRole();
186                        RoleNameType roleName = securityRole.addNewRoleName();
187                        roleName.setStringValue(annotationRoleName);
188                    }
189                }
190            }
191    
192            log.debug("addDeclareRoles(): Exit");
193        }
194    
195    
196        /**
197         * Add @RunAs annotations to the deployment descriptor. XMLBeans are used to read and manipulate
198         * the deployment descriptor as necessary. The DeclareRoles annotation(s) will be converted to
199         * one of the following deployment descriptors:
200         *
201         * <ol>
202         *      <li><run-as> -- Describes a run-as security identity to be used for the execution of a
203         *      component
204         * </ol>
205         *
206         * <p><strong>Note(s):</strong>
207         * <ul>
208         *      <li>The deployment descriptor is the authoritative source so this method ensures that
209         *          existing elements in it are not overwritten by annoations
210         * </ul>
211         *
212         * @param webApp Access to the spec dd
213         * @param annotation    @RunAs annotation
214         * @param cls           Class name with the @RunAs annoation
215         */
216        private static void addRunAs(WebAppType webApp, RunAs annotation, Class cls) {
217            log.debug("addRunAs( [webApp] " + webApp.toString() + "," + '\n' +
218                      "[annotation] " + annotation.toString() + "," + '\n' +
219                      "[cls] " + (cls != null ? cls.getName() : null) + "): Entry");
220    
221            String annotationRunAs = annotation.value();
222            if (!annotationRunAs.equals("")) {
223                ServletType[] servlets = webApp.getServletArray();
224                boolean exists = false;
225                for (ServletType servlet : servlets) {
226                    if (servlet.getServletClass().getStringValue().trim().equals(cls.getName())) {
227                        if (!servlet.isSetRunAs()) {
228                            RunAsType runAsType = servlet.addNewRunAs();
229                            RoleNameType roleName = runAsType.addNewRoleName();
230                            roleName.setStringValue(annotationRunAs);
231                        }
232                        exists = true;
233                        break;
234                    }
235                }
236                if (!exists) {
237                    log.warn("RunAs servlet not found in webApp: " + cls.getName());
238                }
239            }
240    
241            log.debug("addRunAs(): Exit");
242        }
243    
244    }