|
What this is
Other links
The source code/* * Copyright 1999-2004 The Apache Sofware Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.tomcat.modules.aaa; import java.io.File; import org.apache.tomcat.core.BaseInterceptor; import org.apache.tomcat.core.Container; import org.apache.tomcat.core.Context; import org.apache.tomcat.core.ContextManager; import org.apache.tomcat.core.Handler; import org.apache.tomcat.core.Request; import org.apache.tomcat.core.Response; import org.apache.tomcat.core.ServerSession; import org.apache.tomcat.core.TomcatException; import org.apache.tomcat.util.buf.Ascii; import org.apache.tomcat.util.buf.MessageBytes; import org.apache.tomcat.util.http.Parameters; import org.apache.tomcat.util.io.FileUtil; // XXX maybe it's a good idea to use a different model for adding secuirty // constraints - we use Container now because we want to generalize all // per/URL properties. /** * Access control - find if a request matches any web-resource-collection * and set the "required" attributes. * * The spec requires additive checking ( i.e. there is no "best match" * defined, but "all requests that contain a request path that mathces the * URL pattern in the resource collection are subject to the constraing" ). * * In "integrated" mode this interceptor will be no-op, we'll use the * web server ( assuming we can map the security to web-server equivalent * concepts - I think we can do that, but need to experiment with that) */ public class AccessInterceptor extends BaseInterceptor { ContextManager cm; // Security mapping note int secMapNote; // Required roles attribute int reqRolesNote; int reqTransportNote; public AccessInterceptor() { ignoreCase= (File.separatorChar == '\\'); } // -------------------- Ingore case -------------------- boolean ignoreCase=false; /** Use case insensitive match, for windows and similar platforms */ public void setIgnoreCase( boolean b ) { ignoreCase=b; } /* -------------------- Initialization -------------------- */ /** Set the context manager. To keep it simple we don't support * dynamic add/remove for this interceptor. */ public void engineInit(ContextManager cm) throws TomcatException { super.engineInit( cm ); this.cm=cm; // set-up a per/container note for maps secMapNote = cm.getNoteId( ContextManager.CONTAINER_NOTE, "map.security"); // Used for inter-module communication - required role, tr reqRolesNote = cm.getNoteId( ContextManager.REQUEST_NOTE, "required.roles"); reqTransportNote = cm.getNoteId( ContextManager.REQUEST_NOTE, "required.transport"); } public void contextInit( Context ctx) throws TomcatException { String login_type=ctx.getAuthMethod(); if( debug > 0 ) log( "Init " + ctx.getHost() + " " + ctx.getPath() + " " + login_type ); if( "FORM".equals( login_type )) { String page=ctx.getFormLoginPage(); String errorPage = ctx.getFormErrorPage(); if(page==null || errorPage==null) { ctx.log( "Form login without form pages, defaulting to basic " + page + " " + errorPage); BasicAuthHandler baH=new BasicAuthHandler(); baH.setModule( this ); ctx.addServlet( baH ); ctx.addErrorPage( "401", "tomcat.basicAuthHandler"); return; } // Workaround for common error - no "/" at start of page if( ! page.startsWith("/")) { ctx.log("FORM: login page doesn't start with / " + page ); page="/" + page; } if( ! errorPage.startsWith("/")) { ctx.log("FORM: error page doesn't start with / " + errorPage ); errorPage="/" + errorPage; } String cpath=ctx.getPath(); // Workaround for common error - ctx path included if( page.startsWith( cpath + "/" ) ) { if( ! ("".equals(cpath) || "/".equals(cpath)) ) ctx.log("FORM: WARNING, login page starts with " + "context path " + page + " " + cpath ); } else page= cpath + page; if( errorPage.startsWith( cpath + "/" ) ) { if( ! ("/".equals(cpath) || "".equals( cpath )) ) ctx.log("FORM: WARNING, error page starts with " + "context path " + errorPage); } else errorPage= cpath + errorPage; // Adjust login and error paths - avoid computations in handlers ctx.setFormLoginPage( page ); ctx.setFormErrorPage( errorPage ); FormAuthHandler formH=new FormAuthHandler(); formH.setModule( this ); ctx.addServlet( formH ); FormSecurityCheckHandler fscH=new FormSecurityCheckHandler(); fscH.setModule( this ); ctx.addServlet( fscH ); ctx.addErrorPage( "401", "tomcat.formAuthHandler"); // Add mapping for the POST handler String pageP=page.substring( cpath.length()); int lastS=pageP.lastIndexOf( "/" ); String location="/j_security_check"; if( lastS > 0 ) { location=pageP.substring( 0, lastS) + "/j_security_check"; } ctx.addServletMapping( location, "tomcat.formSecurityCheck"); if( debug > 0 ) ctx.log( "Map " + location + " to tomcat.formSecurityCheck for " + page); } else if( "BASIC".equals( login_type )) { BasicAuthHandler baH=new BasicAuthHandler(); baH.setModule( this ); ctx.addServlet( baH ); ctx.addErrorPage( "401", "tomcat.basicAuthHandler"); } else { // if unknown, leave the normal 404 error handler to deal // with unauthorized access. } } // XXX not implemented - will deal with that after everything else works. public void removeContainer( Container ct ) throws TomcatException { } /** */ public void addContainer( Container ct ) throws TomcatException { Context ctx=ct.getContext(); Container ctxCt=ctx.getContainer(); // XXX add the note only if we have a security constraint SecurityConstraints ctxSecurityC=(SecurityConstraints)ctxCt. getNote( secMapNote ); if( ctxSecurityC==null) { ctxSecurityC= new SecurityConstraints(); ctxCt.setNote( secMapNote, ctxSecurityC ); } if( ct.getRoles()!=null || ct.getTransport()!=null ) { if( debug > 0 ) log( "addContainer() " + ctx.getHost() + " " + ctx.getPath() + " " + ct.getPath() ); ctxSecurityC.addContainer( ct ); } } /* -------------------- Request mapping -------------------- */ /** Check if this request requires auth, and if so check the roles. */ public int requestMap( Request req ) { Context ctx=req.getContext(); SecurityConstraints ctxSec=(SecurityConstraints)ctx.getContainer(). getNote( secMapNote ); // do the check for the "special patterns" MessageBytes reqURIMB=req.requestURI(); String ctxPath= ctx.getPath(); int ctxPathLen=ctxPath.length(); // quick test if( reqURIMB.startsWithIgnoreCase( "/META-INF", ctxPathLen) || reqURIMB.startsWithIgnoreCase( "/WEB-INF", ctxPathLen) ) { req.setAttribute("javax.servlet.error.message", "Forbidden directory"); return 403; } // if we don't have any other constraints, return if( ctxSec==null || ctxSec.patterns==0 ) return 0; // fast exit String reqURI = req.requestURI().toString(); /* We don't need this if we normalize the path if( reqURI.indexOf( "//" ) >= 0 ) return 403; */ String path=reqURI.substring( ctxPathLen); String method=req.method().toString(); if( debug > 1 ) log( "checking " + path ); for( int i=0; i< ctxSec.patterns ; i++ ) { Container ct=ctxSec.securityPatterns[i]; if( match( ct, path, method ) ) { req.setSecurityContext( ct ); // Backward compat String roles[]=ct.getRoles(); String methods[]=ct.getMethods(); String transport=ct.getTransport(); if( debug>0) { StringBuffer sb=new StringBuffer("matched "); sb.append(ct.getPath()).append(" "); if(methods!=null) for( int j=0; j< methods.length; j++ ) sb.append(methods[j]).append(" "); sb.append(transport).append(" "); if( roles!=null) for( int j=0; j< roles.length; j++ ) sb.append( roles[j]).append(" "); log( sb.toString()); } if( transport != null && ! "NONE".equals( transport )) { req.setNote( reqTransportNote, transport ); } // roles will be checked by a different interceptor if( roles!= null && roles.length > 0) req.setRequiredRoles( roles ); } } return 0; } /** Handle authorization for requests where certain roles are * requires, and a user/password scheme is used to authenticate * the user ( BASIC, FORM ) and find the user roles. */ public int authorize( Request req, Response response, String roles[] ) { if( req.getSecurityContext() == null && roles==null) { // request doesn't need authentication return OK; } if( roles==null ) roles=req.getSecurityContext().getRoles(); String transp=null; if( req.getSecurityContext() != null ) { transp=(String)req.getNote( reqTransportNote ); } // Check transport. We verify "CONFIDENTIAL" and "INTEGRAL, // other auth modules could do other tests if( debug > 0 ) log( "Transport " + transp ); if( "CONFIDENTIAL".equalsIgnoreCase(transp) || "INTEGRAL".equalsIgnoreCase(transp) ) { if( ! req.scheme().equals("https")) { // We could redirect or do something advanced - but the spec // only requires us to deny access. A nice error handler // would also be nice response.setContentType("text/html"); response.setStatus( 403 ); req.setAttribute("javax.servlet.error.message", "Invalid transport, CONFIDENTIAL required"); return 403;// FORBIDEN } } // no roles - the request may have only transport constraints if( roles == null || roles.length==0 ) { return OK; } // will call authenticate() hooks to get the user String user=req.getRemoteUser(); if( user==null ) return DECLINED; // we know only about user/password auth if( debug > 0 ) log( "Controled access for " + user + " " + req + " " + req.getContainer() ); String userRoles[]= req.getUserRoles(); if ( userRoles == null ) return DECLINED; // no user roles - can't handle for( int i=0; i< userRoles.length; i ++ ) { for( int j=0; j< roles.length; j ++ ) if( userRoles[i]!=null && userRoles[i].equals( roles[j] )) return OK; // found the right role } if( debug > 0 ) log( "UnAuthorized " + roles[0] ); return DECLINED; // couldn't find the role - maybe someone else can } /** Find if a pattern is matched by a container */ boolean match( Container ct, String path, String method ) { String ctPath=ct.getPath(); int ctPathL=ctPath.length(); String ctMethods[]=ct.getMethods(); if( ctMethods != null && ctMethods.length > 0 ) { boolean ok=false; for( int i=0; i< ctMethods.length; i++ ) { if( method.equalsIgnoreCase( ctMethods[i] ) ) { ok=true; break; } } if( ! ok ) return false; // no method matched } // either method is any or we matched the method switch( ct.getMapType() ) { case Container.PREFIX_MAP: // original code: // return path.startsWith( ctPath.substring(0, ctPathL - 2 )); // changed to eliminate the allocation ( will be changed again // when MessageBytes will be used in intercepotrs, now they are // in core if( path.length() < ctPathL - 2 ) return false; // determine how much to match int matchLen = ctPathL - 2; // match up to, but not including the '/' // if more can be matched in the path, include matching the '/' if( path.length() > matchLen ) matchLen++; if( ignoreCase ) { for( int i=0; i< matchLen ; i++ ) { if( Ascii.toLower(path.charAt( i )) != Ascii.toLower(ctPath.charAt( i ))) return false; } } else { for( int i=0; i< matchLen ; i++ ) { if( path.charAt( i ) != ctPath.charAt( i )) return false; } } return true; case Container.EXTENSION_MAP: if( ignoreCase ) return ctPath.substring( 1 ). equalsIgnoreCase(FileUtil.getExtension( path )); else return ctPath.substring( 1 ). equals(FileUtil.getExtension( path )); case Container.PATH_MAP: if( ignoreCase ) return path.equalsIgnoreCase( ctPath ); else return path.equals( ctPath ); } return false; } // -------------------- Implementation methods -------------------- } class SecurityConstraints { Container []securityPatterns; int patterns=0; // implement re-sizeable array later static final int MAX_CONSTRAINTS=30; public SecurityConstraints() { securityPatterns=new Container[MAX_CONSTRAINTS]; } // It's called in a single thread anyway public synchronized void addContainer(Container ct) { //bug 2148 if(patterns>=securityPatterns.length) { Container [] newsecurityPatterns = new Container[MAX_CONSTRAINTS+securityPatterns.length]; System.arraycopy(securityPatterns,0,newsecurityPatterns,0,securityPatterns.length); securityPatterns = newsecurityPatterns; } securityPatterns[ patterns ]= ct; patterns++; } } class BasicAuthHandler extends Handler { // it goes back with the 401 response, not visible to the user static final String errorMessage= " |
... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.