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 }