001: // ========================================================================
002: // Copyright 199-2004 Mort Bay Consulting Pty. Ltd.
003: // ------------------------------------------------------------------------
004: // Licensed under the Apache License, Version 2.0 (the "License");
005: // you may not use this file except in compliance with the License.
006: // You may obtain a copy of the License at
007: // http://www.apache.org/licenses/LICENSE-2.0
008: // Unless required by applicable law or agreed to in writing, software
009: // distributed under the License is distributed on an "AS IS" BASIS,
010: // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011: // See the License for the specific language governing permissions and
012: // limitations under the License.
013: // ========================================================================
014:
015: package org.mortbay.jetty.security;
016:
017: import java.io.IOException;
018: import java.security.Principal;
019: import java.util.Map;
020:
021: import javax.servlet.ServletException;
022: import javax.servlet.http.HttpServletRequest;
023: import javax.servlet.http.HttpServletResponse;
024:
025: import org.mortbay.jetty.Connector;
026: import org.mortbay.jetty.HttpConnection;
027: import org.mortbay.jetty.Request;
028: import org.mortbay.jetty.Response;
029: import org.mortbay.jetty.handler.HandlerWrapper;
030: import org.mortbay.jetty.servlet.PathMap;
031: import org.mortbay.log.Log;
032: import org.mortbay.util.LazyList;
033:
034: /* ------------------------------------------------------------ */
035: /** Handler to enforce SecurityConstraints.
036: *
037: * @author Greg Wilkins (gregw)
038: */
039: public class SecurityHandler extends HandlerWrapper {
040: /* ------------------------------------------------------------ */
041: private String _authMethod = Constraint.__BASIC_AUTH;
042: private UserRealm _userRealm;
043: private ConstraintMapping[] _constraintMappings;
044: private PathMap _constraintMap = new PathMap();
045: private Authenticator _authenticator;
046: private NotChecked _notChecked = new NotChecked();
047:
048: /* ------------------------------------------------------------ */
049: /**
050: * @return Returns the authenticator.
051: */
052: public Authenticator getAuthenticator() {
053: return _authenticator;
054: }
055:
056: /* ------------------------------------------------------------ */
057: /**
058: * @param authenticator The authenticator to set.
059: */
060: public void setAuthenticator(Authenticator authenticator) {
061: _authenticator = authenticator;
062: }
063:
064: /* ------------------------------------------------------------ */
065: /**
066: * @return Returns the userRealm.
067: */
068: public UserRealm getUserRealm() {
069: return _userRealm;
070: }
071:
072: /* ------------------------------------------------------------ */
073: /**
074: * @param userRealm The userRealm to set.
075: */
076: public void setUserRealm(UserRealm userRealm) {
077: _userRealm = userRealm;
078: }
079:
080: /* ------------------------------------------------------------ */
081: /**
082: * @return Returns the contraintMappings.
083: */
084: public ConstraintMapping[] getConstraintMappings() {
085: return _constraintMappings;
086: }
087:
088: /* ------------------------------------------------------------ */
089: /**
090: * @param contraintMappings The contraintMappings to set.
091: */
092: public void setConstraintMappings(
093: ConstraintMapping[] constraintMappings) {
094: _constraintMappings = constraintMappings;
095: if (_constraintMappings != null) {
096: this ._constraintMappings = constraintMappings;
097: _constraintMap.clear();
098:
099: for (int i = 0; i < _constraintMappings.length; i++) {
100: Object mappings = _constraintMap
101: .get(_constraintMappings[i].getPathSpec());
102: mappings = LazyList.add(mappings,
103: _constraintMappings[i]);
104: _constraintMap.put(
105: _constraintMappings[i].getPathSpec(), mappings);
106: }
107: }
108: }
109:
110: /* ------------------------------------------------------------ */
111: public String getAuthMethod() {
112: return _authMethod;
113: }
114:
115: /* ------------------------------------------------------------ */
116: public void setAuthMethod(String method) {
117: if (isStarted() && _authMethod != null
118: && !_authMethod.equals(method))
119: throw new IllegalStateException("Handler started");
120: _authMethod = method;
121: }
122:
123: /* ------------------------------------------------------------ */
124: public boolean hasConstraints() {
125: return _constraintMappings != null
126: && _constraintMappings.length > 0;
127: }
128:
129: /* ------------------------------------------------------------ */
130: public void doStart() throws Exception {
131: if (_authenticator == null) {
132: // Find out the Authenticator.
133: if (Constraint.__BASIC_AUTH.equalsIgnoreCase(_authMethod))
134: _authenticator = new BasicAuthenticator();
135: else if (Constraint.__DIGEST_AUTH
136: .equalsIgnoreCase(_authMethod))
137: _authenticator = new DigestAuthenticator();
138: else if (Constraint.__CERT_AUTH
139: .equalsIgnoreCase(_authMethod))
140: _authenticator = new ClientCertAuthenticator();
141: else if (Constraint.__FORM_AUTH
142: .equalsIgnoreCase(_authMethod))
143: _authenticator = new FormAuthenticator();
144: else
145: Log
146: .warn("Unknown Authentication method:"
147: + _authMethod);
148: }
149:
150: super .doStart();
151: }
152:
153: /* ------------------------------------------------------------ */
154: /*
155: * @see org.mortbay.jetty.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
156: */
157: public void handle(String target, HttpServletRequest request,
158: HttpServletResponse response, int dispatch)
159: throws IOException, ServletException {
160: Request base_request = (request instanceof Request) ? (Request) request
161: : HttpConnection.getCurrentConnection().getRequest();
162: Response base_response = (response instanceof Response) ? (Response) response
163: : HttpConnection.getCurrentConnection().getResponse();
164: UserRealm old_realm = base_request.getUserRealm();
165: try {
166: base_request.setUserRealm(getUserRealm());
167: if (dispatch == REQUEST
168: && !checkSecurityConstraints(target, base_request,
169: base_response)) {
170: base_request.setHandled(true);
171: return;
172: }
173:
174: if (_authenticator instanceof FormAuthenticator
175: && target
176: .endsWith(FormAuthenticator.__J_SECURITY_CHECK)) {
177: _authenticator.authenticate(getUserRealm(), target,
178: base_request, base_response);
179: base_request.setHandled(true);
180: return;
181: }
182:
183: if (getHandler() != null)
184: getHandler()
185: .handle(target, request, response, dispatch);
186: } finally {
187: if (_userRealm != null) {
188: if (base_request.getUserPrincipal() != null)
189: _userRealm.disassociate(base_request
190: .getUserPrincipal());
191: }
192: base_request.setUserRealm(old_realm);
193: }
194: }
195:
196: /* ------------------------------------------------------------ */
197: public boolean checkSecurityConstraints(String pathInContext,
198: Request request, Response response) throws IOException {
199: Object mapping_entries = _constraintMap
200: .getLazyMatches(pathInContext);
201: String pattern = null;
202: Object constraints = null;
203:
204: // for each path match
205: // Add only constraints that have the correct method
206: // break if the matching pattern changes. This allows only
207: // constraints with matching pattern and method to be combined.
208: if (mapping_entries != null) {
209: loop: for (int m = 0; m < LazyList.size(mapping_entries); m++) {
210: Map.Entry entry = (Map.Entry) LazyList.get(
211: mapping_entries, m);
212: Object mappings = entry.getValue();
213: String path_spec = (String) entry.getKey();
214:
215: for (int c = 0; c < LazyList.size(mappings); c++) {
216: ConstraintMapping mapping = (ConstraintMapping) LazyList
217: .get(mappings, c);
218: if (mapping.getMethod() != null
219: && !mapping.getMethod().equalsIgnoreCase(
220: request.getMethod()))
221: continue;
222:
223: if (pattern != null && !pattern.equals(path_spec))
224: break loop;
225:
226: pattern = path_spec;
227: constraints = LazyList.add(constraints, mapping
228: .getConstraint());
229: }
230: }
231:
232: return check(constraints, _authenticator, _userRealm,
233: pathInContext, request, response);
234: }
235:
236: request.setUserPrincipal(_notChecked);
237: return true;
238: }
239:
240: /* ------------------------------------------------------------ */
241: /** Check security contraints
242: * @param constraints
243: * @param authenticator
244: * @param realm
245: * @param pathInContext
246: * @param request
247: * @param response
248: * @return false if the request has failed a security constraint or the authenticator has already sent a response.
249: * @exception IOException
250: */
251: private boolean check(Object constraints,
252: Authenticator authenticator, UserRealm realm,
253: String pathInContext, Request request, Response response)
254: throws IOException {
255: // Combine data and auth constraints
256: int dataConstraint = Constraint.DC_NONE;
257: Object roles = null;
258: boolean unauthenticated = false;
259: boolean forbidden = false;
260:
261: for (int c = 0; c < LazyList.size(constraints); c++) {
262: Constraint sc = (Constraint) LazyList.get(constraints, c);
263:
264: // Combine data constraints.
265: if (dataConstraint > Constraint.DC_UNSET
266: && sc.hasDataConstraint()) {
267: if (sc.getDataConstraint() > dataConstraint)
268: dataConstraint = sc.getDataConstraint();
269: } else
270: dataConstraint = Constraint.DC_UNSET; // ignore all other data constraints
271:
272: // Combine auth constraints.
273: if (!unauthenticated && !forbidden) {
274: if (sc.getAuthenticate()) {
275: if (sc.isAnyRole()) {
276: roles = Constraint.ANY_ROLE;
277: } else {
278: String[] scr = sc.getRoles();
279: if (scr == null || scr.length == 0) {
280: forbidden = true;
281: break;
282: } else {
283: // TODO - this looks inefficient!
284: if (roles != Constraint.ANY_ROLE) {
285: for (int r = scr.length; r-- > 0;)
286: roles = LazyList.add(roles, scr[r]);
287: }
288: }
289: }
290: } else
291: unauthenticated = true;
292: }
293: }
294:
295: // Does this forbid everything?
296: if (forbidden
297: && (!(authenticator instanceof FormAuthenticator) || !((FormAuthenticator) authenticator)
298: .isLoginOrErrorPage(pathInContext))) {
299:
300: response.sendError(HttpServletResponse.SC_FORBIDDEN);
301: return false;
302: }
303:
304: // Handle data constraint
305: if (dataConstraint > Constraint.DC_NONE) {
306: HttpConnection connection = HttpConnection
307: .getCurrentConnection();
308: Connector connector = connection.getConnector();
309:
310: switch (dataConstraint) {
311: case Constraint.DC_INTEGRAL:
312: if (connector.isIntegral(request))
313: break;
314: if (connector.getConfidentialPort() > 0) {
315: String url = connector.getIntegralScheme() + "://"
316: + request.getServerName() + ":"
317: + connector.getIntegralPort()
318: + request.getRequestURI();
319: if (request.getQueryString() != null)
320: url += "?" + request.getQueryString();
321: response.setContentLength(0);
322: response.sendRedirect(url);
323: } else
324: response.sendError(Response.SC_FORBIDDEN, null);
325: return false;
326: case Constraint.DC_CONFIDENTIAL:
327: if (connector.isConfidential(request))
328: break;
329:
330: if (connector.getConfidentialPort() > 0) {
331: String url = connector.getConfidentialScheme()
332: + "://" + request.getServerName() + ":"
333: + connector.getConfidentialPort()
334: + request.getRequestURI();
335: if (request.getQueryString() != null)
336: url += "?" + request.getQueryString();
337:
338: response.setContentLength(0);
339: response.sendRedirect(url);
340: } else
341: response.sendError(Response.SC_FORBIDDEN, null);
342: return false;
343:
344: default:
345: response.sendError(Response.SC_FORBIDDEN, null);
346: return false;
347: }
348: }
349:
350: // Does it fail a role check?
351: if (!unauthenticated && roles != null) {
352: if (realm == null) {
353: Log.warn("Request " + request.getRequestURI()
354: + " failed - no realm");
355: response.sendError(Response.SC_INTERNAL_SERVER_ERROR,
356: "No realm");
357: return false;
358: }
359:
360: Principal user = null;
361:
362: // Handle pre-authenticated request
363: if (request.getAuthType() != null
364: && request.getRemoteUser() != null) {
365: // TODO - is this still needed???
366: user = request.getUserPrincipal();
367: if (user == null)
368: user = realm.authenticate(request.getRemoteUser(),
369: null, request);
370: if (user == null && authenticator != null)
371: user = authenticator.authenticate(realm,
372: pathInContext, request, response);
373: } else if (authenticator != null) {
374: // User authenticator.
375: user = authenticator.authenticate(realm, pathInContext,
376: request, response);
377: } else {
378: // don't know how authenticate
379: Log.warn("Mis-configured Authenticator for "
380: + request.getRequestURI());
381: response.sendError(Response.SC_INTERNAL_SERVER_ERROR,
382: "Configuration error");
383: }
384:
385: // If we still did not get a user
386: if (user == null)
387: return false; // Auth challenge or redirection already sent
388: else if (user == __NOBODY)
389: return true; // The Nobody user indicates authentication in transit.
390:
391: if (roles != Constraint.ANY_ROLE) {
392: boolean inRole = false;
393: for (int r = LazyList.size(roles); r-- > 0;) {
394: if (realm.isUserInRole(user, (String) LazyList.get(
395: roles, r))) {
396: inRole = true;
397: break;
398: }
399: }
400:
401: if (!inRole) {
402: Log.warn("AUTH FAILURE: incorrect role for "
403: + user.getName());
404: /* if ("BASIC".equalsIgnoreCase(authenticator.getAuthMethod()))
405: ((BasicAuthenticator)authenticator).sendChallenge(realm, response);
406: else for TCK */
407: response.sendError(Response.SC_FORBIDDEN,
408: "User not in required role");
409: return false; // role failed.
410: }
411: }
412: } else {
413: request.setUserPrincipal(_notChecked);
414: }
415:
416: return true;
417: }
418:
419: public static Principal __NO_USER = new Principal() {
420: public String getName() {
421: return null;
422: }
423:
424: public String toString() {
425: return "No User";
426: }
427: };
428:
429: public class NotChecked implements Principal {
430: public String getName() {
431: return null;
432: }
433:
434: public String toString() {
435: return "NOT CHECKED";
436: }
437:
438: public SecurityHandler getSecurityHandler() {
439: return SecurityHandler.this ;
440: }
441: };
442:
443: /* ------------------------------------------------------------ */
444: /* ------------------------------------------------------------ */
445: /* ------------------------------------------------------------ */
446: /** Nobody user.
447: * The Nobody UserPrincipal is used to indicate a partial state of
448: * authentication. A request with a Nobody UserPrincipal will be allowed
449: * past all authentication constraints - but will not be considered an
450: * authenticated request. It can be used by Authenticators such as
451: * FormAuthenticator to allow access to logon and error pages within an
452: * authenticated URI tree.
453: */
454: public static Principal __NOBODY = new Principal() {
455: public String getName() {
456: return "Nobody";
457: }
458:
459: public String toString() {
460: return getName();
461: }
462: };
463: }
|