|
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.