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 }