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 package org.apache.geronimo.jetty6.handler; 018 019 import java.io.IOException; 020 import java.security.AccessControlContext; 021 import java.security.AccessControlException; 022 import java.security.Principal; 023 024 import javax.security.auth.Subject; 025 import javax.security.jacc.PolicyContext; 026 import javax.security.jacc.WebResourcePermission; 027 import javax.security.jacc.WebUserDataPermission; 028 import javax.servlet.ServletException; 029 import javax.servlet.http.HttpServletRequest; 030 import javax.servlet.http.HttpServletResponse; 031 032 import org.apache.geronimo.common.GeronimoSecurityException; 033 import org.apache.geronimo.jetty6.JAASJettyPrincipal; 034 import org.apache.geronimo.jetty6.JAASJettyRealm; 035 import org.apache.geronimo.jetty6.JettyContainer; 036 import org.apache.geronimo.security.Callers; 037 import org.apache.geronimo.security.ContextManager; 038 import org.mortbay.jetty.HttpException; 039 import org.mortbay.jetty.Request; 040 import org.mortbay.jetty.Response; 041 import org.mortbay.jetty.security.Authenticator; 042 import org.mortbay.jetty.security.FormAuthenticator; 043 import org.mortbay.jetty.security.SecurityHandler; 044 045 public class JettySecurityHandler extends SecurityHandler { 046 047 private String policyContextID; 048 049 private JAASJettyPrincipal defaultPrincipal; 050 051 private String formLoginPath; 052 053 private JAASJettyRealm realm; 054 055 public JettySecurityHandler(Authenticator authenticator, 056 JAASJettyRealm userRealm, 057 String policyContextID, 058 Subject defaultSubject) { 059 setAuthenticator(authenticator); 060 this.policyContextID = policyContextID; 061 062 if (authenticator instanceof FormAuthenticator) { 063 String formLoginPath = ((FormAuthenticator) authenticator).getLoginPage(); 064 if (formLoginPath.indexOf('?') > 0) { 065 formLoginPath = formLoginPath.substring(0, formLoginPath.indexOf('?')); 066 } 067 this.formLoginPath = formLoginPath; 068 } else { 069 formLoginPath = null; 070 } 071 072 /** 073 * Register our default principal with the ContextManager 074 */ 075 if (defaultSubject == null) { 076 defaultSubject = ContextManager.EMPTY; 077 } 078 this.defaultPrincipal = generateDefaultPrincipal(defaultSubject); 079 080 setUserRealm(userRealm); 081 this.realm = userRealm; 082 assert realm != null; 083 } 084 085 public boolean hasConstraints() { 086 return true; 087 } 088 089 public void doStop(JettyContainer jettyContainer) throws Exception { 090 try { 091 super.doStop(); 092 } 093 finally { 094 jettyContainer.removeRealm(realm.getSecurityRealmName()); 095 } 096 } 097 098 /* ------------------------------------------------------------ */ 099 /* 100 * @see org.mortbay.jetty.security.SecurityHandler#handle(java.lang.String, 101 * javax.servlet.http.HttpServletRequest, 102 * javax.servlet.http.HttpServletResponse, int) 103 */ 104 public void handle(String target, HttpServletRequest request, 105 HttpServletResponse response, int dispatch) throws IOException, 106 ServletException { 107 String old_policy_id = PolicyContext.getContextID(); 108 Callers oldCallers = ContextManager.getCallers(); 109 110 try { 111 PolicyContext.setContextID(policyContextID); 112 PolicyContext.setHandlerData(request); 113 114 super.handle(target, request, response, dispatch); 115 } finally { 116 PolicyContext.setContextID(old_policy_id); 117 ContextManager.popCallers(oldCallers); 118 } 119 } 120 121 // public static Subject getCurrentRoleDesignate(String role) { 122 // return ((JettySecurityHandler) (WebAppContext.getCurrentWebAppContext() 123 // .getSecurityHandler())).getRoleDesignate(role); 124 // } 125 // 126 // private Subject getRoleDesignate(String roleName) { 127 // return (Subject) roleDesignates.get(roleName); 128 // } 129 130 /** 131 * Check the security constraints using JACC. 132 * 133 * @param pathInContext path in context 134 * @param request HTTP request 135 * @param response HTTP response 136 * @return true if the path in context passes the security check, false if 137 * it fails or a redirection has occured during authentication. 138 */ 139 public boolean checkSecurityConstraints(String pathInContext, Request request, Response response) throws IOException { 140 if (formLoginPath != null) { 141 String pathToBeTested = (pathInContext.indexOf('?') > 0 ? pathInContext 142 .substring(0, pathInContext.indexOf('?')) 143 : pathInContext); 144 145 if (pathToBeTested.equals(formLoginPath)) { 146 return true; 147 } 148 } 149 150 try { 151 String transportType; 152 if (request.isSecure()) { 153 transportType = "CONFIDENTIAL"; 154 } else if (request.getConnection().isIntegral(request)) { 155 transportType = "INTEGRAL"; 156 } else { 157 transportType = "NONE"; 158 } 159 String substitutedPathInContext = pathInContext; 160 if (substitutedPathInContext.indexOf("%3A") > -1) 161 substitutedPathInContext = substitutedPathInContext.replaceAll("%3A", "%3A%3A"); 162 if (substitutedPathInContext.indexOf(":") > -1) 163 substitutedPathInContext = substitutedPathInContext.replaceAll(":", "%3A"); 164 165 166 Authenticator authenticator = getAuthenticator(); 167 boolean isAuthenticated = false; 168 169 if (authenticator instanceof FormAuthenticator 170 && pathInContext.endsWith(FormAuthenticator.__J_SECURITY_CHECK)) { 171 /** 172 * This is a post request to __J_SECURITY_CHECK. Stop now after authentication. 173 * Whether or not authentication succeeded, we return. 174 */ 175 authenticator.authenticate(realm, pathInContext, request, response); 176 return false; 177 } 178 // attempt to access an unprotected resource that is not the 179 // j_security_check. 180 // if we are logged in, return the logged in principal. 181 if (request != null) { 182 // null response appears to prevent redirect to login page 183 Principal user = authenticator.authenticate(realm, pathInContext, 184 request, null); 185 if (user == null || user == SecurityHandler.__NOBODY) { 186 //TODO use run-as as nextCaller if present 187 ContextManager.setCallers(defaultPrincipal.getSubject(), defaultPrincipal.getSubject()); 188 request.setUserPrincipal(new NotChecked()); 189 } else if (user != null) { 190 isAuthenticated = true; 191 } 192 } 193 194 195 AccessControlContext acc = ContextManager.getCurrentContext(); 196 197 /** 198 * JACC v1.0 section 4.1.1 199 */ 200 WebUserDataPermission wudp = new WebUserDataPermission(substitutedPathInContext, new String[]{request.getMethod()}, transportType); 201 acc.checkPermission(wudp); 202 203 WebResourcePermission webResourcePermission = new WebResourcePermission(request); 204 /** 205 * JACC v1.0 section 4.1.2 206 */ 207 if (isAuthenticated) { 208 //current user is logged in, this is the actual check 209 acc.checkPermission(webResourcePermission); 210 } else { 211 //user is not logged in: if access denied, try to log them in. 212 try { 213 acc.checkPermission(webResourcePermission); 214 } catch (AccessControlException e) { 215 //not logged in: try to log them in. 216 Principal user = authenticator.authenticate(realm, pathInContext, request, response); 217 if (user == SecurityHandler.__NOBODY) { 218 return true; 219 } 220 if (user == null) { 221 throw e; 222 } 223 } 224 } 225 226 } catch (HttpException he) { 227 response.sendError(he.getStatus(), he.getReason()); 228 return false; 229 } catch (AccessControlException ace) { 230 if (!response.isCommitted()) { 231 response.sendError(403); 232 } 233 return false; 234 } 235 return true; 236 } 237 238 /** 239 * Generate the default principal from the security config. 240 * 241 * @param defaultSubject The default subject. 242 * @return the default principal 243 * @throws org.apache.geronimo.common.GeronimoSecurityException 244 * if the default principal cannot be constructed 245 */ 246 protected JAASJettyPrincipal generateDefaultPrincipal(Subject defaultSubject) 247 throws GeronimoSecurityException { 248 249 if (defaultSubject == null) { 250 throw new GeronimoSecurityException( 251 "Unable to generate default principal"); 252 } 253 254 JAASJettyPrincipal result = new JAASJettyPrincipal("default"); 255 256 result.setSubject(defaultSubject); 257 258 return result; 259 } 260 261 }