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 }